int main (int argc, char *argv[]) { #if !defined(NDEBUG) || defined(OIIO_CI) || defined(OIIO_CODE_COVERAGE) // For the sake of test time, reduce the default iterations for DEBUG, // CI, and code coverage builds. Explicit use of --iters or --trials // will override this, since it comes before the getargs() call. iterations /= 10; ntrials = 1; #endif getargs (argc, argv); // Initialize imgA.reset (ImageSpec (xres, yres, channels, TypeDesc::FLOAT)); imgB.reset (ImageSpec (xres, yres, channels, TypeDesc::FLOAT)); imgR.reset (ImageSpec (xres, yres, channels, TypeDesc::FLOAT)); float red[3] = { 1, 0, 0 }; float green[3] = { 0, 1, 0 }; float blue[3] = { 0, 0, 1 }; float black[3] = { 0, 0, 0 }; ImageBufAlgo::fill (imgA, red, green, red, green); ImageBufAlgo::fill (imgB, blue, blue, black, black); // imgA.write ("A.exr"); // imgB.write ("B.exr"); test_compute (); return unit_test_failures; }
int main (int argc, char *argv[]) { getargs (argc, argv); // Initialize imgA.reset (ImageSpec (xres, yres, channels, TypeDesc::FLOAT)); imgB.reset (ImageSpec (xres, yres, channels, TypeDesc::FLOAT)); imgR.reset (ImageSpec (xres, yres, channels, TypeDesc::FLOAT)); float red[3] = { 1, 0, 0 }; float green[3] = { 0, 1, 0 }; float blue[3] = { 0, 0, 1 }; float black[3] = { 0, 0, 0 }; ImageBufAlgo::fill (imgA, red, green, red, green); ImageBufAlgo::fill (imgB, blue, blue, black, black); // imgA.write ("A.exr"); // imgB.write ("B.exr"); test_compute (); return unit_test_failures; }
bool ImageBufAlgo::make_kernel (ImageBuf &dst, string_view name, float width, float height, float depth, bool normalize) { int w = std::max (1, (int)ceilf(width)); int h = std::max (1, (int)ceilf(height)); int d = std::max (1, (int)ceilf(depth)); // Round up size to odd w |= 1; h |= 1; d |= 1; ImageSpec spec (w, h, 1 /*channels*/, TypeDesc::FLOAT); spec.depth = d; spec.x = -w/2; spec.y = -h/2; spec.z = -d/2; spec.full_x = spec.x; spec.full_y = spec.y; spec.full_z = spec.z; spec.full_width = spec.width; spec.full_height = spec.height; spec.full_depth = spec.depth; dst.reset (spec); if (Filter2D *filter = Filter2D::create (name, width, height)) { // Named continuous filter from filter.h for (ImageBuf::Iterator<float> p (dst); ! p.done(); ++p) p[0] = (*filter)((float)p.x(), (float)p.y()); delete filter; } else if (name == "binomial") { // Binomial filter float *wfilter = ALLOCA (float, width); for (int i = 0; i < width; ++i) wfilter[i] = binomial (width-1, i); float *hfilter = (height == width) ? wfilter : ALLOCA (float, height); if (height != width) for (int i = 0; i < height; ++i) hfilter[i] = binomial (height-1, i); float *dfilter = ALLOCA (float, depth); if (depth == 1) dfilter[0] = 1; else for (int i = 0; i < depth; ++i) dfilter[i] = binomial (depth-1, i); for (ImageBuf::Iterator<float> p (dst); ! p.done(); ++p) p[0] = wfilter[p.x()-spec.x] * hfilter[p.y()-spec.y] * dfilter[p.z()-spec.z]; } else {
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; }
bool ImageBufAlgo::histogram_draw (ImageBuf &R, const std::vector<imagesize_t> &histogram) { // Fail if there are no bins to draw. int bins = histogram.size(); if (bins == 0) { R.error ("There are no bins to draw, the histogram is empty"); return false; } // Check R and modify it if needed. int height = R.spec().height; if (R.spec().format != TypeDesc::TypeFloat || R.nchannels() != 1 || R.spec().width != bins) { ImageSpec newspec = ImageSpec (bins, height, 1, TypeDesc::FLOAT); R.reset ("dummy", newspec); } // Fill output image R with white color. ImageBuf::Iterator<float, float> r (R); for ( ; ! r.done(); ++r) r[0] = 1; // Draw histogram left->right, bottom->up. imagesize_t max = *std::max_element (histogram.begin(), histogram.end()); for (int b = 0; b < bins; b++) { int bin_height = (int) ((float)histogram[b]/(float)max*height + 0.5f); if (bin_height != 0) { // Draw one bin at column b. for (int j = 1; j <= bin_height; j++) { int row = height - j; r.pos (b, row); r[0] = 0; } } } return true; }
bool ImageBufAlgo::zover (ImageBuf &R, const ImageBuf &A, const ImageBuf &B, bool z_zeroisinf, ROI roi, int nthreads) { const ImageSpec &specR = R.spec(); const ImageSpec &specA = A.spec(); const ImageSpec &specB = B.spec(); int nchannels_R, nchannels_A, nchannels_B; int alpha_R, alpha_A, alpha_B; int z_R, z_A, z_B; int colors_R, colors_A, colors_B; bool initialized_R = decode_over_channels (R, nchannels_R, alpha_R, z_R, colors_R); bool initialized_A = decode_over_channels (A, nchannels_A, alpha_A, z_A, colors_A); bool initialized_B = decode_over_channels (B, nchannels_B, alpha_B, z_B, colors_B); if (! initialized_A || ! initialized_B) { R.error ("Can't 'zover' uninitialized images"); return false; } // Fail if the input images don't have a Z channel. if (z_A < 0 || z_B < 0 || (initialized_R && z_R < 0)) { R.error ("'zover' requires Z channels"); return false; } // Fail if the input images don't have an alpha channel. if (alpha_A < 0 || alpha_B < 0 || (initialized_R && alpha_R < 0)) { R.error ("'zover' requires alpha channels"); return false; } // Fail for mismatched channel counts if (colors_A != colors_B || colors_A < 1) { R.error ("Can't 'zover' images with mismatched color channel counts (%d vs %d)", colors_A, colors_B); return false; } // Fail for unaligned alpha or z channels if (alpha_A != alpha_B || z_A != z_B || (initialized_R && alpha_R != alpha_A) || (initialized_R && z_R != z_A)) { R.error ("Can't 'zover' images with mismatched channel order", colors_A, colors_B); return false; } // At present, this operation only supports ImageBuf's containing // float pixel data. if ((initialized_R && specR.format != TypeDesc::TypeFloat) || specA.format != TypeDesc::TypeFloat || specB.format != TypeDesc::TypeFloat) { R.error ("Unsupported pixel data format combination '%s = %s zover %s'", specR.format, specA.format, specB.format); return false; } // Uninitialized R -> size it to the union of A and B. if (! initialized_R) { ImageSpec newspec = specA; set_roi (newspec, roi_union (get_roi(specA), get_roi(specB))); R.reset ("zover", newspec); } // Specified ROI -> use it. Unspecified ROI -> initialize from R. if (! roi.defined()) roi = get_roi (R.spec()); parallel_image (boost::bind (over_impl<float,float,float>, boost::ref(R), boost::cref(A), boost::cref(B), _1, true, z_zeroisinf), roi, nthreads); return ! R.has_error(); }
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); }
OIIO_NAMESPACE_BEGIN bool ImageBufAlgo::from_IplImage (ImageBuf &dst, const IplImage *ipl, TypeDesc convert) { if (! ipl) { DASSERT (0 && "ImageBufAlgo::fromIplImage called with NULL ipl"); dst.error ("Passed NULL source IplImage"); return false; } #ifdef USE_OPENCV TypeDesc srcformat; switch (ipl->depth) { case int(IPL_DEPTH_8U) : srcformat = TypeDesc::UINT8; break; case int(IPL_DEPTH_8S) : srcformat = TypeDesc::INT8; break; case int(IPL_DEPTH_16U) : srcformat = TypeDesc::UINT16; break; case int(IPL_DEPTH_16S) : srcformat = TypeDesc::INT16; break; case int(IPL_DEPTH_32F) : srcformat = TypeDesc::FLOAT; break; case int(IPL_DEPTH_64F) : srcformat = TypeDesc::DOUBLE; break; default: DASSERT (0 && "unknown IplImage type"); dst.error ("Unsupported IplImage depth %d", (int)ipl->depth); return false; } TypeDesc dstformat = (convert != TypeDesc::UNKNOWN) ? convert : srcformat; ImageSpec spec (ipl->width, ipl->height, ipl->nChannels, dstformat); // N.B. The OpenCV headers say that ipl->alphaChannel, // ipl->colorModel, and ipl->channelSeq are ignored by OpenCV. if (ipl->dataOrder != IPL_DATA_ORDER_PIXEL) { // We don't handle separate color channels, and OpenCV doesn't either dst.error ("Unsupported IplImage data order %d", (int)ipl->dataOrder); return false; } dst.reset (dst.name(), spec); size_t pixelsize = srcformat.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; // Block copy and convert convert_image (spec.nchannels, spec.width, spec.height, 1, ipl->imageData, srcformat, pixelsize, linestep, 0, dst.pixeladdr(0,0), dstformat, spec.pixel_bytes(), spec.scanline_bytes(), 0); // FIXME - honor dataOrder. I'm not sure if it is ever used by // OpenCV. Fix when it becomes a problem. // OpenCV uses BGR ordering // FIXME: what do they do with alpha? if (spec.nchannels >= 3) { float pixel[4]; for (int y = 0; y < spec.height; ++y) { for (int x = 0; x < spec.width; ++x) { dst.getpixel (x, y, pixel, 4); float tmp = pixel[0]; pixel[0] = pixel[2]; pixel[2] = tmp; dst.setpixel (x, y, pixel, 4); } } } // FIXME -- the copy and channel swap should happen all as one loop, // probably templated by type. return true; #else dst.error ("fromIplImage not supported -- no OpenCV support at compile time"); return false; #endif }
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; }
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; }
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; }