Beispiel #1
0
static void
time_read_imagebuf ()
{
    ImageBuf ib (input_filename.string(), imagecache);
    ib.read (0, 0, true, TypeDesc::TypeFloat);
    imagecache->invalidate_all (true);
}
// Test ability to do a maketx directly from an ImageBuf
void
test_maketx_from_imagebuf()
{
    std::cout << "test make_texture from ImageBuf\n";
    // Make a checkerboard
    const int WIDTH = 16, HEIGHT = 16, CHANNELS = 3;
    ImageSpec spec (WIDTH, HEIGHT, CHANNELS, TypeDesc::FLOAT);
    ImageBuf A (spec);
    float pink[] = { .5, .3, .3 }, green[] = { .1, .5, .1 };
    ImageBufAlgo::checker (A, 4, 4, 4, pink, green);

    // Write it
    const char *pgname = "oiio-pgcheck.tx";
    remove (pgname);  // Remove it first
    ImageSpec configspec;
    ImageBufAlgo::make_texture (ImageBufAlgo::MakeTxTexture, A,
                                pgname, configspec);

    // Read it back and compare it
    ImageBuf B (pgname);
    B.read ();
    ImageBufAlgo::CompareResults comparison;
    ImageBufAlgo::compare (A, B, 0, 0, comparison);
    OIIO_CHECK_EQUAL (comparison.nwarn, 0);
    OIIO_CHECK_EQUAL (comparison.nfail, 0);
    remove (pgname);  // clean up
}
Beispiel #3
0
ImageBuf *
SMT::reconstructBig()
{
	if( verbose )cout << "INFO: Reconstructing Big\n";
	ImageBuf *tileBuf = NULL;

	//Load tilemap from SMF
	ImageBuf *tilemapBuf = NULL;
	if( is_smf( tilemapFile ) ) {
		SMF sourcesmf(tilemapFile);
		tilemapBuf = sourcesmf.getTilemap();
	}
	// Else load tilemap from image
	if( !tilemapBuf ) {
		tilemapBuf = new ImageBuf( tilemapFile );
		tilemapBuf->read(0,0,false,TypeDesc::UINT);
		if( !tilemapBuf->initialized() ) {
			delete tilemapBuf;
			if( !quiet )printf("ERROR: %s cannot be loaded.\n",
				tilemapFile.c_str());
			return NULL;
		}
	}
	//TODO Else load tilemap from csv

	unsigned int *tilemap = (unsigned int *)tilemapBuf->localpixels();
	int xtiles = tilemapBuf->spec().width;
	int ztiles = tilemapBuf->spec().height;

	// allocate enough data for our large image
	ImageSpec bigSpec( xtiles * tileRes, ztiles * tileRes, 4, TypeDesc::UINT8 );
	ImageBuf *bigBuf = new ImageBuf( "big", bigSpec );

	// Loop through tile index
	for( int z = 0; z < ztiles; ++z ) {
		for( int x = 0; x < xtiles; ++x ) {
			int tilenum = tilemap[z * xtiles + x];
			tileBuf = getTile(tilenum);

			int xbegin = tileRes * x;
			int ybegin = tileRes * z;
			ImageBufAlgo::paste(*bigBuf, xbegin, ybegin, 0, 0, *tileBuf);

			delete [] (unsigned char *)tileBuf->localpixels();
			delete tileBuf;
			if( verbose )printf("\033[0GINFO: Processing tile %i of %i.",
				z * xtiles + x, xtiles * ztiles );
		}
	}
	cout << endl;

	delete tilemapBuf;
	if( is_smf( tilemapFile ) ) delete [] tilemap;
	
	return bigBuf;	
}
Beispiel #4
0
static bool
read_input (const std::string &filename, ImageBuf &img,
            int subimage=0, int miplevel=0)
{
    if (img.subimage() >= 0 && img.subimage() == subimage)
        return true;

    if (img.init_spec (filename, subimage, miplevel) && 
        img.read (subimage, miplevel, false, TypeDesc::FLOAT))
        return true;

    return false;
}
Beispiel #5
0
static bool
read_input (const std::string &filename, ImageBuf &img,
            int subimage=0, int miplevel=0)
{
    if (img.subimage() >= 0 && img.subimage() == subimage)
        return true;

    if (img.init_spec (filename, subimage, miplevel) &&
            img.read (subimage, miplevel, false, TypeDesc::FLOAT))
        return true;

    std::cerr << "oiiotool ERROR: Could not read " << filename << ":\n\t"
              << img.geterror() << "\n";
    return false;
}
Beispiel #6
0
static bool
read_input (const std::string &filename, ImageBuf &img, 
            ImageCache *cache, int subimage=0, int miplevel=0)
{
    if (img.subimage() >= 0 && 
            img.subimage() == subimage && img.miplevel() == miplevel)
        return true;

    img.reset (filename, cache);
    if (img.read (subimage, miplevel, false, TypeDesc::TypeFloat))
        return true;

    std::cerr << "idiff ERROR: Could not read " << filename << ":\n\t"
              << img.geterror() << "\n";
    return false;
}
Beispiel #7
0
static bool
read_input (const std::string &filename, ImageBuf &img,
            int subimage=0, int miplevel=0)
{
    if (img.subimage() >= 0 && img.subimage() == subimage)
        return true;

    if (img.init_spec (filename, subimage, miplevel)) {
        // Force a read now for reasonable-sized first images in the
        // file. This can greatly speed up the multithread case for
        // tiled images by not having multiple threads working on the
        // same image lock against each other on the file handle.
        // We guess that "reasonable size" is 200 MB, that's enough to
        // hold a 4k RGBA float image.  Larger things will 
        // simply fall back on ImageCache.
        bool forceread = (img.spec().image_bytes() < 200*1024*1024);
        return img.read (subimage, miplevel, forceread, TypeDesc::FLOAT);
    }

    return false;
}
Beispiel #8
0
ImageRec::ImageRec (ImageRec &img, int subimage_to_copy,
                    int miplevel_to_copy, bool writable, bool copy_pixels)
    : m_name(img.name()), m_elaborated(true),
      m_metadata_modified(false), m_pixels_modified(false),
      m_was_output(false),
      m_imagecache(img.m_imagecache)
{
    img.read ();
    int first_subimage = std::max (0, subimage_to_copy);
    int subimages = (subimage_to_copy < 0) ? img.subimages() : 1;
    m_subimages.resize (subimages);
    for (int s = 0;  s < subimages;  ++s) {
        int srcsub = s + first_subimage;
        int first_miplevel = std::max (0, miplevel_to_copy);
        int miplevels = (miplevel_to_copy < 0) ? img.miplevels(srcsub) : 1;
        m_subimages[s].m_miplevels.resize (miplevels);
        m_subimages[s].m_specs.resize (miplevels);
        for (int m = 0;  m < miplevels;  ++m) {
            int srcmip = m + first_miplevel;
            const ImageBuf &srcib (img(srcsub,srcmip));
            const ImageSpec &srcspec (*img.spec(srcsub,srcmip));
            ImageBuf *ib = NULL;
            if (writable || img.pixels_modified() || !copy_pixels) {
                // Make our own copy of the pixels
                ib = new ImageBuf (srcspec);
                if (copy_pixels)
                    ib->copy_pixels (srcib);
            } else {
                // The other image is not modified, and we don't need to be
                // writable, either.
                ib = new ImageBuf (img.name(), srcib.imagecache());
                bool ok = ib->read (srcsub, srcmip, false /*force*/,
                                    img.m_input_dataformat /*convert*/);
                ASSERT (ok);
            }
            m_subimages[s].m_miplevels[m].reset (ib);
            m_subimages[s].m_specs[m] = srcspec;
        }
    }
}
Beispiel #9
0
bool
ImageRec::read (ReadPolicy readpolicy)
{
    if (elaborated())
        return true;
    static ustring u_subimages("subimages"), u_miplevels("miplevels");
    static boost::regex regex_sha ("SHA-1=[[:xdigit:]]*[ ]*");
    int subimages = 0;
    ustring uname (name());
    if (! m_imagecache->get_image_info (uname, 0, 0, u_subimages,
                                        TypeDesc::TypeInt, &subimages)) {
        error ("file not found: \"%s\"", name());
        return false;  // Image not found
    }
    m_subimages.resize (subimages);
    bool allok = true;
    for (int s = 0;  s < subimages;  ++s) {
        int miplevels = 0;
        m_imagecache->get_image_info (uname, s, 0, u_miplevels,
                                      TypeDesc::TypeInt, &miplevels);
        m_subimages[s].m_miplevels.resize (miplevels);
        m_subimages[s].m_specs.resize (miplevels);
        for (int m = 0;  m < miplevels;  ++m) {
            // Force a read now for reasonable-sized first images in the
            // file. This can greatly speed up the multithread case for
            // tiled images by not having multiple threads working on the
            // same image lock against each other on the file handle.
            // We guess that "reasonable size" is 50 MB, that's enough to
            // hold a 2048x1536 RGBA float image.  Larger things will 
            // simply fall back on ImageCache.
            bool forceread = (s == 0 && m == 0 &&
                              m_imagecache->imagespec(uname,s,m)->image_bytes() < 50*1024*1024);
            ImageBuf *ib = new ImageBuf (name(), m_imagecache);

            // If we were requested to bypass the cache, force a full read.
            if (readpolicy & ReadNoCache)
                forceread = true;

            // Convert to float unless asked to keep native.
            TypeDesc convert = (readpolicy & ReadNative)
                             ? ib->nativespec().format : TypeDesc::FLOAT;
            if (! forceread &&
                convert != TypeDesc::UINT8 && convert != TypeDesc::UINT16 &&
                convert != TypeDesc::HALF &&  convert != TypeDesc::FLOAT) {
                // If we're still trying to use the cache but it doesn't
                // support the native type, force a full read.
                forceread = true;
            }

            bool ok = ib->read (s, m, forceread, convert);
            if (!ok)
                error ("%s", ib->geterror());
            allok &= ok;
            // Remove any existing SHA-1 hash from the spec.
            ib->specmod().erase_attribute ("oiio:SHA-1");
            std::string desc = ib->spec().get_string_attribute ("ImageDescription");
            if (desc.size())
                ib->specmod().attribute ("ImageDescription",
                                         boost::regex_replace (desc, regex_sha, ""));

            m_subimages[s].m_miplevels[m].reset (ib);
            m_subimages[s].m_specs[m] = ib->spec();
            // For ImageRec purposes, we need to restore a few of the
            // native settings.
            const ImageSpec &nativespec (ib->nativespec());
            // m_subimages[s].m_specs[m].format = nativespec.format;
            m_subimages[s].m_specs[m].tile_width  = nativespec.tile_width;
            m_subimages[s].m_specs[m].tile_height = nativespec.tile_height;
            m_subimages[s].m_specs[m].tile_depth  = nativespec.tile_depth;
        }
    }

    m_time = Filesystem::last_write_time (name());
    m_elaborated = true;
    return allok;
}
Beispiel #10
0
bool
SMF::saveMinimap()
{
	if( verbose )cout << "INFO: saveMinimap\n";

	char filename[256];
	sprintf( filename, "%s.smf", outPrefix.c_str() );

	fstream smf(filename, ios::binary | ios::in | ios::out);
	smf.seekp(minimapPtr);

	unsigned char *pixels;
	if( is_smf(minimapFile) ) {
		// Copy from SMF
		pixels = new unsigned char[MINIMAP_SIZE];

		ifstream inFile(minimapFile.c_str(), ifstream::in);
		inFile.seekg(header.minimapPtr);
		inFile.read( (char *)pixels, MINIMAP_SIZE);
		inFile.close();

		smf.write( (char *)pixels, MINIMAP_SIZE);
		smf.close();
		delete [] pixels;
		return false;
	}

	//OpenImageIO
	ROI roi(	0, 1024,
				0, 1024,
				0, 1,
				0, 4);
	ImageSpec imageSpec( roi.xend, roi.yend, roi.chend, TypeDesc::UINT8 );
	

	// Load image file
	ImageBuf *imageBuf = new ImageBuf( minimapFile );
	imageBuf->read( 0, 0, false, TypeDesc::UINT8 );
//FIXME attempt to generate minimap from tile files.

	if( !imageBuf->initialized() ) {
		// Create from height
		imageBuf->reset( minimapFile );
		imageBuf->read( 0, 0, false, TypeDesc::UINT8 );
	}

	if( !imageBuf->initialized() ) {
		// Create blank
		imageBuf->reset( "minimap", imageSpec);
	}

	imageSpec = imageBuf->specmod();
	ImageBuf fixBuf;
	// Fix channels
	if( imageSpec.nchannels != roi.chend ) {
		int map[] = {2,1,0,3};
		float fill[] = {0,0,0,255};
		ImageBufAlgo::channels(fixBuf, *imageBuf, roi.chend, map, fill);
		imageBuf->copy(fixBuf);
		fixBuf.clear();
	}

	// Fix dimensions
	if( imageSpec.width != roi.xend || imageSpec.height != roi.yend ) {
		printf( "\tWARNING: %s is (%i,%i), wanted (%i, %i), Resampling.\n",
			minimapFile.c_str(), imageSpec.width, imageSpec.height, roi.xend, roi.yend );
		ImageBufAlgo::resample(fixBuf, *imageBuf, true, roi);
		imageBuf->copy(fixBuf);
		fixBuf.clear();
	}

	pixels = (unsigned char *)imageBuf->localpixels();

	// setup DXT1 Compression
	nvtt::InputOptions inputOptions;
	inputOptions.setTextureLayout( nvtt::TextureType_2D, 1024, 1024 );
	inputOptions.setMipmapData( pixels, 1024, 1024 );

	nvtt::CompressionOptions compressionOptions;
	compressionOptions.setFormat( nvtt::Format_DXT1 );
	if( slowcomp ) compressionOptions.setQuality( nvtt::Quality_Normal ); 
	else compressionOptions.setQuality( nvtt::Quality_Fastest ); 

	nvtt::OutputOptions outputOptions;
	outputOptions.setOutputHeader( false );

	NVTTOutputHandler *outputHandler = new NVTTOutputHandler(MINIMAP_SIZE + 1);
	outputOptions.setOutputHandler( outputHandler );

	nvtt::Compressor compressor;
	compressor.process( inputOptions, compressionOptions, outputOptions );

	// Write data to smf
	smf.write( outputHandler->buffer, MINIMAP_SIZE );
	delete outputHandler;

	smf.close();
	delete imageBuf;		
	return false;
}
Beispiel #11
0
bool
SMF::saveMetal()
{
	if( verbose )cout << "INFO: saveMetal\n";

	// Dimensions of displacement map.
	ImageBuf *imageBuf = NULL;
	ROI roi(	0, width * 32,  // xbegin, xend
				0, length * 32, // ybegin, yend
				0, 1,               // zbegin, zend
				0, 1);              // chbegin, chend
	ImageSpec imageSpec( roi.xend, roi.yend, roi.chend, TypeDesc::UINT8 );

	if( is_smf(metalFile) ) {
		// Load from smf
		SMF sourcesmf(metalFile);
		imageBuf = sourcesmf.getMetal();
	}
	if( !imageBuf ) {
		//load from image
		imageBuf = new ImageBuf(metalFile);
		imageBuf->read( 0, 0, false, TypeDesc::UINT8 );
		if( !imageBuf->initialized() ) {
			delete imageBuf;
			imageBuf = NULL;
		}
	}
	if( !imageBuf ) {
		// Generate blank
		imageBuf = new ImageBuf( "metal", imageSpec );
	}

	imageSpec = imageBuf->specmod();
	ImageBuf fixBuf;
	// Fix the number of channels
	if( imageSpec.nchannels != roi.chend ) {
		int map[] = {0};
		ImageBufAlgo::channels(fixBuf, *imageBuf, roi.chend, map);
		imageBuf->copy(fixBuf);
		fixBuf.clear();
	}

	// Fix the size
	if ( imageSpec.width != roi.xend || imageSpec.height != roi.yend ) {
		if( verbose )
			printf( "\tWARNING: %s is (%i,%i), wanted (%i, %i), Resampling.\n",
			metalFile.c_str(), imageSpec.width, imageSpec.height, roi.xend, roi.yend );
		ImageBufAlgo::resample(fixBuf, *imageBuf, true, roi);
		imageBuf->copy(fixBuf);
		fixBuf.clear();
	}

	unsigned char *pixels = (unsigned char *)imageBuf->localpixels();


	char filename[256];
	sprintf( filename, "%s.smf", outPrefix.c_str() );

	fstream smf(filename, ios::binary | ios::in | ios::out);
	smf.seekp(metalPtr);

	// write the data to the smf
	smf.write( (char *)pixels, imageBuf->spec().image_bytes() );
	smf.close();

	delete imageBuf;
	if( is_smf( metalFile ) ) delete [] pixels;

	return false;
}
Beispiel #12
0
static void
make_texturemap (const char *maptypename = "texture map")
{
    if (filenames.size() != 1) {
        std::cerr << "maketx ERROR: " << maptypename 
                  << " requires exactly one input filename\n";
        exit (EXIT_FAILURE);
    }

    if (! Filesystem::exists (filenames[0])) {
        std::cerr << "maketx ERROR: \"" << filenames[0] << "\" does not exist\n";
        exit (EXIT_FAILURE);
    }
    if (outputfilename.empty()) {
        std::string ext = boost::filesystem::extension (filenames[0]);
        int notextlen = (int) filenames[0].length() - (int) ext.length();
        outputfilename = std::string (filenames[0].begin(),
                                      filenames[0].begin() + notextlen);
        outputfilename += ".tx";
    }

    // When was the input file last modified?
    std::time_t in_time = boost::filesystem::last_write_time (filenames[0]);

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

    ImageBuf src (filenames[0]);
    src.init_spec (filenames[0], 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;

    // Figure out which data format we want for output
    if (! dataformatname.empty()) {
        if (dataformatname == "uint8")
            out_dataformat = TypeDesc::UINT8;
        else if (dataformatname == "int8" || dataformatname == "sint8")
            out_dataformat = TypeDesc::INT8;
        else if (dataformatname == "uint16")
            out_dataformat = TypeDesc::UINT16;
        else if (dataformatname == "int16" || dataformatname == "sint16")
            out_dataformat = TypeDesc::INT16;
        else if (dataformatname == "half")
            out_dataformat = TypeDesc::HALF;
        else if (dataformatname == "float")
            out_dataformat = TypeDesc::FLOAT;
        else if (dataformatname == "double")
            out_dataformat = TypeDesc::DOUBLE;
    }
    
    
    // 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 (prman) out_dataformat = set_prman_options (out_dataformat);
    else if (oiio) out_dataformat = set_oiio_options (out_dataformat);
    
    // 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));

    if (verbose)
        std::cout << "Reading file: " << filenames[0] << std::endl;
    Timer readtimer;
    if (! src.read (0, 0, read_local)) {
        std::cerr 
            << "maketx ERROR: Could not read \"" 
            << filenames[0] << "\" : " << src.geterror() << "\n";
        exit (EXIT_FAILURE);
    }
    stat_readtime += readtimer();
    
    // If requested - and we're a constant color - make a tiny texture instead
    std::vector<float> constantColor(src.nchannels());
    bool isConstantColor = ImageBufAlgo::isConstantColor (src, &constantColor[0]);
    
    if (isConstantColor && constant_color_detect) {
        int newwidth = std::max (1, std::min (src.spec().width, tile[0]));
        int newheight = std::max (1, std::min (src.spec().height, tile[1]));
        
        ImageSpec newspec = src.spec();
        newspec.x = 0;
        newspec.y = 0;
        newspec.z = 0;
        newspec.width = newwidth;
        newspec.height = newheight;
        newspec.depth = 1;
        newspec.full_x = 0;
        newspec.full_y = 0;
        newspec.full_z = 0;
        newspec.full_width = newspec.width;
        newspec.full_height = newspec.height;
        newspec.full_depth = newspec.depth;
        
        // Reset the image, to a new image, at the new size
        std::string name = src.name() + ".constant_color";
        src.reset(name, newspec);
        
        ImageBufAlgo::fill (src, &constantColor[0]);
        
        if (verbose) {
            std::cout << "  Constant color image detected. ";
            std::cout << "Creating " << newspec.width << "x" << newspec.height << " texture instead.\n";
        }
    }
    
    // If requested - and we're a monochrome image - drop the extra channels
    if (monochrome_detect && (src.nchannels() > 1) && ImageBufAlgo::isMonochrome(src)) {
        ImageBuf newsrc(src.name() + ".monochrome", src.spec());
        ImageBufAlgo::setNumChannels (newsrc, src, 1);
        src = newsrc;
        if (verbose) {
            std::cout << "  Monochrome image detected. Converting to single channel texture.\n";
        }
    }
    // Or, if we've otherwise explicitly requested to write out a
    // specific number of channels, do it.
    else if ((nchannels > 0) && (nchannels != src.nchannels())) {
        ImageBuf newsrc(src.name() + ".channels", src.spec());
        ImageBufAlgo::setNumChannels (newsrc, src, nchannels);
        src = newsrc;
        if (verbose) {
            std::cout << "  Overriding number of channels to " << nchannels << "\n";
        }
    }
    
    if (shadowmode) {
        // Some special checks for shadow maps
        if (src.spec().nchannels != 1) {
            std::cerr << "maketx ERROR: shadow maps require 1-channel images,\n"
                      << "\t\"" << filenames[0] << "\" is " 
                      << src.spec().nchannels << " channels\n";
            exit (EXIT_FAILURE);
        }
        // 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;
    }

    // Copy the input spec
    const ImageSpec &srcspec = src.spec();
    ImageSpec dstspec = srcspec;

    // Make the output not a crop window
    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;
    bool orig_was_crop = (srcspec.x != 0 || srcspec.y != 0 || srcspec.z != 0 ||
                          srcspec.full_width != srcspec.width ||
                          srcspec.full_height != srcspec.height ||
                          srcspec.full_depth != srcspec.depth);

    // Make the output tiled, regardless of input
    dstspec.tile_width  = tile[0];
    dstspec.tile_height = tile[1];
    dstspec.tile_depth  = tile[2];

    // Always use ZIP compression
    dstspec.attribute ("compression", "zip");
    // Ugh, the line above seems to trigger a bug in the tiff library.
    // Maybe a bug in libtiff zip compression for tiles?  So let's
    // stick to the default compression.

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

    dstspec.attribute ("Software", full_command_line);
    
    if (shadowmode) {
        dstspec.attribute ("textureformat", "Shadow");
        if (prman_metadata)
            dstspec.attribute ("PixarTextureFormat", "Shadow");
    } else if (envlatlmode) {
        dstspec.attribute ("textureformat", "LatLong Environment");
        swrap = "periodic";
        twrap = "clamp";
        if (prman_metadata)
            dstspec.attribute ("PixarTextureFormat", "Latlong Environment");
    } else {
        dstspec.attribute ("textureformat", "Plain Texture");
        if(prman_metadata)
            dstspec.attribute ("PixarTextureFormat", "Plain Texture");
    }

    if (Mcam != Imath::M44f(0.0f))
        dstspec.attribute ("worldtocamera", TypeDesc::TypeMatrix, &Mcam);
    if (Mscr != Imath::M44f(0.0f))
        dstspec.attribute ("worldtoscreen", TypeDesc::TypeMatrix, &Mscr);

    // FIXME - check for valid strings in the wrap mode
    if (! shadowmode) {
        std::string wrapmodes = (swrap.size() ? swrap : wrap) + ',' + 
                                (twrap.size() ? twrap : wrap);
        dstspec.attribute ("wrapmodes", wrapmodes);
    }
    
    if(fovcot == 0.0f) {
        fovcot = static_cast<float>(srcspec.full_width) / 
            static_cast<float>(srcspec.full_height);
    }
    dstspec.attribute ("fovcot", fovcot);

    if (separate)
        dstspec.attribute ("planarconfig", "separate");
    else {
        dstspec.erase_attribute("planarconfig");
        dstspec.erase_attribute("tiff:planarconfig");
    }
    // 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 (checknan && (srcspec.format.basetype == TypeDesc::FLOAT ||
                     srcspec.format.basetype == TypeDesc::HALF ||
                     srcspec.format.basetype == TypeDesc::DOUBLE)) {
        found_nonfinite = false;
        parallel_image (check_nan_block, &src, &src,
                        dstspec.x, dstspec.x+dstspec.width,
                        dstspec.y, dstspec.y+dstspec.height, nthreads);
        if (found_nonfinite) {
            if (found_nonfinite > 3)
                std::cerr << "maketx ERROR: ...and Nan/Inf at "
                          << (found_nonfinite-3) << " other pixels\n";
            exit (EXIT_FAILURE);
        }
    }

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

    // Handle resize to power of two, if called for
    if (! noresize  &&  ! 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.depth != srcspec.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
    if (envlatlmode && ! src_samples_border && 
        (iequals(fileformatname,"openexr") || iends_with(outputfilename,".exr")))
        do_resize = true;

    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 == srcspec.format) {
            // Even more special case, no format change -- just use
            // the original copy.
            toplevel = &src;
        } else {
            parallel_image (copy_block, &dst, &src,
                            dstspec.x, dstspec.x+dstspec.width,
                            dstspec.y, dstspec.y+dstspec.height, nthreads);
        }
    } else {
        // Resize
        if (verbose)
            std::cout << "  Resizing image to " << dstspec.width 
                      << " x " << dstspec.height << std::endl;
        if (filtername == "box" && filter->width() == 1.0f)
            parallel_image (resize_block, &dst, &src,
                            dstspec.x, dstspec.x+dstspec.width,
                            dstspec.y, dstspec.y+dstspec.height, nthreads);
        else
            parallel_image (resize_block_HQ, &dst, &src,
                            dstspec.x, dstspec.x+dstspec.width,
                            dstspec.y, dstspec.y+dstspec.height, nthreads);
    }
    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;
    
    // FIXME: We need to do real dictionary style partial updates on the
    //        ImageDescription. I.e., set one key without affecting the
    //        other keys. But in the meantime, just clear it out if
    //        it appears the incoming image was a maketx style texture.
    
    if ((desc.find ("SHA-1=") != std::string::npos) || 
        (desc.find ("ConstantColor=") != std::string::npos)) {
        desc = "";
    }
    
    
    // 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)
            std::cout << "  SHA-1: " << hash_digest << std::endl;
        updatedDesc = true;
    }
    
    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)
            std::cout << "  ConstantColor: " << os.str() << std::endl;
        updatedDesc = true;
    }
    
    if (updatedDesc) {
        dstspec.attribute ("ImageDescription", desc);
    }



    // Write out, and compute, the mipmap levels for the speicifed image
    
    std::string outformat = fileformatname.empty() ? outputfilename : fileformatname;
    write_mipmap (*toplevel, dstspec, outputfilename, outformat, out_dataformat,
                  !shadowmode && !nomipmap);

    // If using update mode, stamp the output file with a modification time
    // matching that of the input file.
    if (updatemode)
        boost::filesystem::last_write_time (outputfilename, in_time);
}
Beispiel #13
0
bool
SMF::saveTilemap()
{
	if( verbose )cout << "INFO: saveTilemap\n";

	char filename[256];
	sprintf( filename, "%s.smf", outPrefix.c_str() );

	fstream smf(filename, ios::binary | ios::in | ios::out);
	smf.seekp(tilesPtr);

	// Tiles Header
	int nTileFiles = smtList.size();
	smf.write( (char *)&nTileFiles, 4);
	smf.write( (char *)&nTiles, 4);
	if(verbose)printf( "    %i tiles referenced in %i files\n", nTiles, nTileFiles );

	// SMT Names
	for(unsigned int i = 0; i < smtList.size(); ++i) {
		if( verbose )printf( "\t%i %s\n", smtTiles[i], smtList[i].c_str() );
		smf.write( (char *)&smtTiles[i], 4);
		smf.write( smtList[i].c_str(), smtList[i].size() +1 );
	}

	// Dimensions of displacement map.
	ImageBuf *imageBuf = NULL;
	ROI roi(	0, width * 16,  // xbegin, xend
				0, length * 16, // ybegin, yend
				0, 1,               // zbegin, zend
				0, 1);              // chbegin, chend
	ImageSpec imageSpec( roi.xend, roi.yend, roi.chend, TypeDesc::UINT );

	if( is_smf(tilemapFile) ) {
		// Load from SMF
		SMF sourcesmf(tilemapFile);
		imageBuf = sourcesmf.getTilemap();
	}
   	if( !imageBuf ) {
		// load image file
		imageBuf = new ImageBuf( tilemapFile );
		imageBuf->read( 0, 0, false, TypeDesc::UINT );
		if( !imageBuf->initialized() ) {
			delete imageBuf;
			imageBuf = NULL;
		}
	}
	if( !imageBuf ) {
		// Generate blank
		imageBuf = new ImageBuf( "tilemap", imageSpec );
		for ( unsigned int i = 0; i < imageSpec.image_pixels(); ++i )
			((unsigned int *)imageBuf->localpixels())[ i ] = i;
	}

	imageSpec = imageBuf->specmod();
	ImageBuf fixBuf;
	// Fix the number of channels
	if( imageSpec.nchannels != roi.chend ) {
		int map[] = {0};
		ImageBufAlgo::channels(fixBuf, *imageBuf, roi.chend, map);
		imageBuf->copy(fixBuf);
		fixBuf.clear();
	}

	// Fix the size
	// FIXME image should never be resized, instead tiling either from an edge or centred.
	if ( imageSpec.width != roi.xend || imageSpec.height != roi.yend ) {
		if( verbose )
			printf( "\tWARNING: %s is (%i,%i), wanted (%i, %i), Resampling.\n",
			tilemapFile.c_str(), imageSpec.width, imageSpec.height, roi.xend, roi.yend );
		ImageBufAlgo::resample(fixBuf, *imageBuf, false, roi);
		imageBuf->copy(fixBuf);
		fixBuf.clear();
	}

	unsigned int *pixels = (unsigned int *)imageBuf->localpixels();

	// write the data to the smf
	smf.write( (char *)pixels, imageBuf->spec().image_bytes() );
	smf.close();

	delete imageBuf;
	if( is_smf( tilemapFile ) ) delete [] pixels;

	return false;
}
Beispiel #14
0
ImageBuf *
SMT::buildBig()
{
	if( verbose && sourceFiles.size() > 1 ) {
		cout << "INFO: Collating source images\n";
		cout << "    nFiles: " << sourceFiles.size() << endl;
		cout << "    stride: " << stride << endl;
		if( sourceFiles.size() % stride != 0 )
			cout << "WARNING: number of source files isnt divisible by stride,"
				" black spots will exist\n";
	}

	// Get values to fix
	ImageBuf *regionBuf = new ImageBuf(sourceFiles[0]);
	regionBuf->read(0,0,false, TypeDesc::UINT8);
	if( !regionBuf->initialized() ) {
		if( !quiet )printf("ERROR: could not build big image\n");
		return NULL;
	}
	ImageSpec regionSpec = regionBuf->spec();
	delete regionBuf;

	// Construct the big buffer
	ImageSpec bigSpec(
		regionSpec.width * stride,
		regionSpec.height * sourceFiles.size() / stride,
		4,
		TypeDesc::UINT8 );
	if( verbose )printf("    Allocating: (%i,%i)%i\n",
		bigSpec.width, bigSpec.height, bigSpec.nchannels );
	ImageBuf *bigBuf = new ImageBuf( "big", bigSpec );

	// Fill the alpha channel
	const float fill[] = { 0, 0, 0, 255 };
	ImageBufAlgo::fill( *bigBuf, fill );

	// Collate the source Files
	int nFiles = sourceFiles.size();
	for( int i = 0; i < nFiles; ++i ) {
		if( verbose )printf( "\033[0G    copying %i of %i, %s",
			i + 1, nFiles, sourceFiles[i].c_str() );
		regionBuf = new ImageBuf( sourceFiles[i] );
		regionBuf->read( 0, 0, false, TypeDesc::UINT8 );
		if( !regionBuf->initialized() ) {
			if( !quiet )printf( "\nERROR: %s could not be loaded.\n",
				sourceFiles[i].c_str() );
			continue;
		}
		regionSpec = regionBuf->spec();

		int x = regionSpec.width * (i % stride);
		int y = regionSpec.height * (i / stride);
		y = bigSpec.height - y - regionSpec.height;

		ImageBufAlgo::paste( *bigBuf, x, y, 0, 0, *regionBuf );	
	}
	if( verbose )cout << endl;

	// rescale constructed big image to wanted size
	ROI roi( 0, width * 512 , 0, length * 512, 0, 1, 0, 4 );
	ImageBuf fixBuf;
	if( bigSpec.width != roi.xend || bigSpec.height != roi.yend ) {
		if( verbose )
			printf( "WARNING: Image is (%i,%i), wanted (%i, %i),"
				" Resampling.\n",
			bigSpec.width, bigSpec.height, roi.xend, roi.yend );
		ImageBufAlgo::resample( fixBuf, *bigBuf, true, roi );
		bigBuf->copy( fixBuf );
	}

	return bigBuf;
}
Beispiel #15
0
IplImage *
ImageBufAlgo::to_IplImage (const ImageBuf &src)
{
#ifdef USE_OPENCV
    ImageBuf tmp = src;
    ImageSpec spec = tmp.spec();

    // Make sure the image buffer is initialized.
    if (!tmp.initialized() && !tmp.read(tmp.subimage(), tmp.miplevel(), true)) {
        DASSERT (0 && "Could not initialize ImageBuf.");
        return NULL;
    }

    int dstFormat;
    TypeDesc dstSpecFormat;
    if (spec.format == TypeDesc(TypeDesc::UINT8)) {
        dstFormat = IPL_DEPTH_8U;
        dstSpecFormat = spec.format;
    } else if (spec.format == TypeDesc(TypeDesc::INT8)) {
        dstFormat = IPL_DEPTH_8S;
        dstSpecFormat = spec.format;
    } else if (spec.format == TypeDesc(TypeDesc::UINT16)) {
        dstFormat = IPL_DEPTH_16U;
        dstSpecFormat = spec.format;
    } else if (spec.format == TypeDesc(TypeDesc::INT16)) {
        dstFormat = IPL_DEPTH_16S;
        dstSpecFormat = spec.format;
    } else if (spec.format == TypeDesc(TypeDesc::HALF)) {
        dstFormat = IPL_DEPTH_32F;
        // OpenCV does not support half types. Switch to float instead.
        dstSpecFormat = TypeDesc(TypeDesc::FLOAT);
    } else if (spec.format == TypeDesc(TypeDesc::FLOAT)) {
        dstFormat = IPL_DEPTH_32F;
        dstSpecFormat = spec.format;
    } else if (spec.format == TypeDesc(TypeDesc::DOUBLE)) {
        dstFormat = IPL_DEPTH_64F;
        dstSpecFormat = spec.format;
    } else {
        DASSERT (0 && "Unknown data format in ImageBuf.");
        return NULL;
    }
    IplImage *ipl = cvCreateImage(cvSize(spec.width, spec.height), dstFormat, spec.nchannels);
    if (!ipl) {
        DASSERT (0 && "Unable to create IplImage.");
        return NULL;
    }

    size_t pixelsize = dstSpecFormat.size() * spec.nchannels;
    // Account for the origin in the line step size, to end up with the
    // standard OIIO origin-at-upper-left:
    size_t linestep = ipl->origin ? -ipl->widthStep : ipl->widthStep;

    bool converted = convert_image(spec.nchannels, spec.width, spec.height, 1,
                                   tmp.localpixels(), spec.format,
                                   spec.pixel_bytes(), spec.scanline_bytes(), 0,
                                   ipl->imageData, dstSpecFormat,
                                   pixelsize, linestep, 0);

    if (!converted) {
        DASSERT (0 && "convert_image failed.");
        cvReleaseImage(&ipl);
        return NULL;
    }

    // OpenCV uses BGR ordering
    if (spec.nchannels == 3) {
        cvCvtColor(ipl, ipl, CV_RGB2BGR);
    } else if (spec.nchannels == 4) {
        cvCvtColor(ipl, ipl, CV_RGBA2BGRA);
    }

    return ipl;
#else
    return NULL;
#endif
}
Beispiel #16
0
bool
SMF::saveGrass()
{
	if( verbose )cout << "INFO: saveGrass\n";

	SMFEHGrass *grassHeader = NULL;
	for( unsigned int i = 0; i < extraHeaders.size(); ++i ) {
		if( extraHeaders[ i ]->type == 1 )
			grassHeader = reinterpret_cast<SMFEHGrass *>( extraHeaders[ i ] );
	}
	if( !grassHeader )return true;

	ImageBuf *imageBuf = NULL;
	ROI roi(	0, width * 16,
				0, length * 16,
				0, 1,
				0, 1);
	ImageSpec imageSpec(roi.xend, roi.yend, roi.chend, TypeDesc::UINT8 );

	if( is_smf(grassFile) ) {
		// Load from SMF
		SMF sourcesmf(grassFile);
		imageBuf = sourcesmf.getGrass();
	}
	if( !imageBuf ) {
		// Load image file
		imageBuf = new ImageBuf( grassFile );
		imageBuf->read( 0, 0, false, TypeDesc::UINT8 );
		if( imageBuf->initialized() ) {
			delete imageBuf;
			imageBuf = NULL;
		}
	}
	if( !imageBuf ) {
		// Generate blank
		imageBuf = new ImageBuf( "grass", imageSpec );
	}

	imageSpec = imageBuf->specmod();
	ImageBuf fixBuf;

	// Fix the number of channels
	if( imageSpec.nchannels != roi.chend ) {
		int map[] = {0};
		ImageBufAlgo::channels(fixBuf, *imageBuf, roi.chend, map );
		imageBuf->copy( fixBuf );
		fixBuf.clear();
	}

	// Fix the Dimensions
	if ( imageSpec.width != roi.xend || imageSpec.height != roi.yend ) {
		if( verbose )
			printf( "\tWARNING: %s is (%i,%i), wanted (%i, %i) Resampling.\n",
			typeFile.c_str(), imageSpec.width, imageSpec.height, roi.xend, roi.yend);
		ImageBufAlgo::resample(fixBuf, *imageBuf, false, roi);
		imageBuf->copy( fixBuf );
		fixBuf.clear();		
	}

	unsigned char *pixels = (unsigned char *)imageBuf->localpixels();

	char filename[256];
	sprintf( filename, "%s.smf", outPrefix.c_str() );

	if( verbose )printf( "    Source: %s.\n", grassFile.c_str() );

	fstream smf(filename, ios::binary | ios::in | ios::out);
	smf.seekp(grassHeader->grassPtr);

	smf.write( (char *)pixels, imageBuf->spec().image_bytes() );
	smf.close();
	
	delete imageBuf;
	if( is_smf( grassFile ) ) delete [] pixels;

	return false;
}
Beispiel #17
0
bool
SMF::saveHeight()
{
	if( verbose )cout << "INFO: saveHeight\n";
	// Dimensions of displacement map.
	ImageBuf *imageBuf = NULL;
	ROI roi(	0, width * 64 + 1,  // xbegin, xend
				0, length * 64 + 1, // ybegin, yend
				0, 1,               // zbegin, zend
				0, 1);              // chbegin, chend
	ImageSpec imageSpec( roi.xend, roi.yend, roi.chend, TypeDesc::UINT16 );

	if( is_smf(heightFile) ) {
		// Load from SMF
		SMF sourcesmf(heightFile);
		imageBuf = sourcesmf.getHeight();
	}
   	if( !imageBuf ) {
		// load image file
		imageBuf = new ImageBuf( heightFile );
		imageBuf->read( 0, 0, false, TypeDesc::UINT16 );
		if( !imageBuf->initialized() ) {
			delete imageBuf;
			imageBuf = NULL;
		}
	}
	if( !imageBuf ) {
		// Generate blank
		imageBuf = new ImageBuf( "height", imageSpec );
	}

	imageSpec = imageBuf->specmod();
	ImageBuf fixBuf;
	// Fix the number of channels
	if( imageSpec.nchannels != roi.chend ) {
		int map[] = {0};
		ImageBufAlgo::channels(fixBuf, *imageBuf, roi.chend, map);
		imageBuf->copy(fixBuf);
		fixBuf.clear();
	}

	// Fix the size
	if ( imageSpec.width != roi.xend || imageSpec.height != roi.yend ) {
		if( verbose )
			printf( "\tWARNING: %s is (%i,%i), wanted (%i, %i), Resampling.\n",
			heightFile.c_str(), imageSpec.width, imageSpec.height, roi.xend, roi.yend );
		ImageBufAlgo::resample(fixBuf, *imageBuf, true, roi);
		imageBuf->copy(fixBuf);
		fixBuf.clear();
	}

	// Invert height
	if ( invert ) {
		ImageSpec fixSpec(roi.xend, roi.yend, roi.chend, TypeDesc::UINT16);
		fixBuf.reset( "fixBuf",  fixSpec );
		const float fill[] = {65535};
		ImageBufAlgo::fill(fixBuf, fill);
		ImageBufAlgo::sub(*imageBuf, fixBuf, *imageBuf);
		fixBuf.clear();
	}

	// FIXME filter to remove stepping artifacts from 8bit images,
//	if ( lowpass ) {
//	}
	
	unsigned short *pixels = (unsigned short *)imageBuf->localpixels();

	// write height data to smf.
	char filename[256];
	sprintf( filename, "%s.smf", outPrefix.c_str() );

	fstream smf(filename, ios::binary | ios::in| ios::out);
	smf.seekp(heightPtr);

	smf.write( (char *)pixels, imageBuf->spec().image_bytes() );
	smf.close();
	delete imageBuf;
	if( is_smf( heightFile ) ) delete [] pixels;

	return false;
}
Beispiel #18
0
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;
}