static bool write_mipmap (ImageBufAlgo::MakeTextureMode mode, ImageBuf &img, const ImageSpec &outspec_template, std::string outputfilename, ImageOutput *out, TypeDesc outputdatatype, bool mipmap, Filter2D *filter, const ImageSpec &configspec, std::ostream &outstream, double &stat_writetime, double &stat_miptime) { bool envlatlmode = (mode == ImageBufAlgo::MakeTxEnvLatl); ImageSpec outspec = outspec_template; outspec.set_format (outputdatatype); if (mipmap && !out->supports ("multiimage") && !out->supports ("mipmap")) { outstream << "maketx ERROR: \"" << outputfilename << "\" format does not support multires images\n"; return false; } 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 bool src_samples_border; if (envlatlmode && !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); Timer writetimer; if (! out->open (outputfilename.c_str(), outspec)) { outstream << "maketx ERROR: Could not open \"" << outputfilename << "\" : " << out->geterror() << "\n"; return false; } // Write out the image bool verbose = configspec.get_int_attribute ("maketx:verbose"); if (verbose) { outstream << " Writing file: " << outputfilename << std::endl; outstream << " Filter \"" << filter->name() << "\" width = " << filter->width() << "\n"; outstream << " Top level is " << formatres(outspec) << std::endl; } if (! img.write (out)) { // ImageBuf::write transfers any errors from the ImageOutput to // the ImageBuf. outstream << "maketx ERROR: Write failed \" : " << img.geterror() << "\n"; out->close (); return false; } stat_writetime += writetimer(); if (mipmap) { // Mipmap levels: if (verbose) outstream << " Mipmapping...\n" << std::flush; std::vector<std::string> mipimages; std::string mipimages_unsplit = configspec.get_string_attribute ("maketx:mipimages"); if (mipimages_unsplit.length()) Strutil::split (mipimages_unsplit, mipimages, ";"); ImageBuf tmp; ImageBuf *big = &img, *small = &tmp; while (outspec.width > 1 || outspec.height > 1) { Timer miptimer; ImageSpec smallspec; if (mipimages.size()) { // Special case -- the user specified a custom MIP level small->reset (mipimages[0]); small->read (0, 0, true, TypeDesc::FLOAT); smallspec = small->spec(); if (smallspec.nchannels != outspec.nchannels) { outstream << "WARNING: Custom mip level \"" << mipimages[0] << " had the wrong number of channels.\n"; ImageBuf *t = new ImageBuf (mipimages[0], smallspec); ImageBufAlgo::setNumChannels(*t, *small, outspec.nchannels); std::swap (t, small); delete t; } smallspec.tile_width = outspec.tile_width; smallspec.tile_height = outspec.tile_height; smallspec.tile_depth = outspec.tile_depth; mipimages.erase (mipimages.begin()); } else { // Resize a factor of two smaller 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); // Trick: to get the resize working properly, we reset // both display and pixel windows to match, and have 0 // offset, AND doctor the big image to have its display // and pixel windows match. Don't worry, the texture // engine doesn't care what the upper MIP levels have // for the window sizes, it uses level 0 to determine // the relatinship between texture 0-1 space (display // window) and the pixels. smallspec.x = 0; smallspec.y = 0; smallspec.full_x = 0; smallspec.full_y = 0; small->alloc (smallspec); // Realocate with new size big->set_full (big->xbegin(), big->xend(), big->ybegin(), big->yend(), big->zbegin(), big->zend()); if (filter->name() == "box" && filter->width() == 1.0f) ImageBufAlgo::parallel_image (boost::bind(resize_block, small, big, _1, envlatlmode), OIIO::get_roi(small->spec())); else ImageBufAlgo::parallel_image (boost::bind(resize_block_HQ, small, big, _1, filter), OIIO::get_roi(small->spec())); } 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. ImageOutput::OpenMode mode = out->supports ("mipmap") ? ImageOutput::AppendMIPLevel : ImageOutput::AppendSubimage; if (! out->open (outputfilename.c_str(), outspec, mode)) { outstream << "maketx ERROR: Could not append \"" << outputfilename << "\" : " << out->geterror() << "\n"; return false; } if (! small->write (out)) { // ImageBuf::write transfers any errors from the // ImageOutput to the ImageBuf. outstream << "maketx ERROR writing \"" << outputfilename << "\" : " << small->geterror() << "\n"; out->close (); return false; } stat_writetime += writetimer(); if (verbose) { outstream << " " << formatres(smallspec) << std::endl; } std::swap (big, small); } } if (verbose) outstream << " Wrote file: " << outputfilename << std::endl; writetimer.reset (); writetimer.start (); if (! out->close ()) { outstream << "maketx ERROR writing \"" << outputfilename << "\" : " << out->geterror() << "\n"; return false; } stat_writetime += writetimer (); return true; }