bool ImageBufAlgo::reorient (ImageBuf &dst, const ImageBuf &src, int nthreads) { ImageBuf tmp; bool ok = false; switch (src.orientation()) { case 1: ok = dst.copy (src); break; case 2: ok = ImageBufAlgo::flop (dst, src); break; case 3: ok = ImageBufAlgo::rotate180 (dst, src); break; case 4: ok = ImageBufAlgo::flip (dst, src); break; case 5: ok = ImageBufAlgo::rotate270 (tmp, src); if (ok) ok = ImageBufAlgo::flop (dst, tmp); else dst.error ("%s", tmp.geterror()); break; case 6: ok = ImageBufAlgo::rotate90 (dst, src); break; case 7: ok = ImageBufAlgo::flip (tmp, src); if (ok) ok = ImageBufAlgo::rotate90 (dst, tmp); else dst.error ("%s", tmp.geterror()); break; case 8: ok = ImageBufAlgo::rotate270 (dst, src); break; } dst.set_orientation (1); return ok; }
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; }
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; }
static void print_stats (Oiiotool &ot, const std::string &filename, const ImageSpec &originalspec, int subimage=0, int miplevel=0, bool indentmip=false) { const char *indent = indentmip ? " " : " "; ImageBuf input; if (! read_input (filename, input, subimage, miplevel)) { ot.error ("stats", input.geterror()); return; } PixelStats stats; if (! computePixelStats (stats, input)) { std::string err = input.geterror(); ot.error ("stats", Strutil::format ("unable to compute: %s", err.empty() ? "unspecified error" : err.c_str())); return; } // The original spec is used, otherwise the bit depth will // be reported incorrectly (as FLOAT) unsigned int maxval = (unsigned int)get_intsample_maxval (originalspec); printf ("%sStats Min: ", indent); for (unsigned int i=0; i<stats.min.size(); ++i) { print_stats_num (stats.min[i], maxval, true); printf (" "); } print_stats_footer (maxval); printf ("\n"); printf ("%sStats Max: ", indent); for (unsigned int i=0; i<stats.max.size(); ++i) { print_stats_num (stats.max[i], maxval, true); printf (" "); } print_stats_footer (maxval); printf ("\n"); printf ("%sStats Avg: ", indent); for (unsigned int i=0; i<stats.avg.size(); ++i) { print_stats_num (stats.avg[i], maxval, false); printf (" "); } print_stats_footer (maxval); printf ("\n"); printf ("%sStats StdDev: ", indent); for (unsigned int i=0; i<stats.stddev.size(); ++i) { print_stats_num (stats.stddev[i], maxval, false); printf (" "); } print_stats_footer (maxval); printf ("\n"); printf ("%sStats NanCount: ", indent); for (unsigned int i=0; i<stats.nancount.size(); ++i) { printf ("%llu ", (unsigned long long)stats.nancount[i]); } printf ("\n"); printf ("%sStats InfCount: ", indent); for (unsigned int i=0; i<stats.infcount.size(); ++i) { printf ("%llu ", (unsigned long long)stats.infcount[i]); } printf ("\n"); printf ("%sStats FiniteCount: ", indent); for (unsigned int i=0; i<stats.finitecount.size(); ++i) { printf ("%llu ", (unsigned long long)stats.finitecount[i]); } printf ("\n"); if (input.deep()) { const DeepData *dd (input.deepdata()); size_t npixels = dd->pixels(); size_t totalsamples = 0, emptypixels = 0; size_t maxsamples = 0, minsamples = std::numeric_limits<size_t>::max(); size_t maxsamples_npixels = 0; float mindepth = std::numeric_limits<float>::max(); float maxdepth = -std::numeric_limits<float>::max(); Imath::V3i maxsamples_pixel(-1,-1,-1), minsamples_pixel(-1,-1,-1); Imath::V3i mindepth_pixel(-1,-1,-1), maxdepth_pixel(-1,-1,-1); Imath::V3i nonfinite_pixel(-1,-1,-1); int nonfinite_pixel_samp(-1), nonfinite_pixel_chan(-1); size_t sampoffset = 0; int nchannels = dd->channels(); int depthchannel = -1; long long nonfinites = 0; for (int c = 0; c < nchannels; ++c) if (Strutil::iequals (originalspec.channelnames[c], "Z")) depthchannel = c; int xend = originalspec.x + originalspec.width; int yend = originalspec.y + originalspec.height; int zend = originalspec.z + originalspec.depth; size_t p = 0; std::vector<size_t> nsamples_histogram; for (int z = originalspec.z; z < zend; ++z) { for (int y = originalspec.y; y < yend; ++y) { for (int x = originalspec.x; x < xend; ++x, ++p) { size_t samples = input.deep_samples (x, y, z); totalsamples += samples; if (samples == maxsamples) ++maxsamples_npixels; if (samples > maxsamples) { maxsamples = samples; maxsamples_pixel.setValue (x, y, z); maxsamples_npixels = 1; } if (samples < minsamples) minsamples = samples; if (samples == 0) ++emptypixels; if (samples >= nsamples_histogram.size()) nsamples_histogram.resize (samples+1, 0); nsamples_histogram[samples] += 1; for (unsigned int s = 0; s < samples; ++s) { for (int c = 0; c < nchannels; ++c) { float d = input.deep_value (x, y, z, c, s); if (! isfinite(d)) { if (nonfinites++ == 0) { nonfinite_pixel.setValue (x, y, z); nonfinite_pixel_samp = s; nonfinite_pixel_chan = c; } } if (depthchannel == c) { if (d < mindepth) { mindepth = d; mindepth_pixel.setValue (x, y, z); } if (d > maxdepth) { maxdepth = d; maxdepth_pixel.setValue (x, y, z); } } } } sampoffset += samples; } } } printf ("%sMin deep samples in any pixel : %llu\n", indent, (unsigned long long)minsamples); printf ("%sMax deep samples in any pixel : %llu\n", indent, (unsigned long long)maxsamples); printf ("%s%llu pixel%s had the max of %llu samples, including (x=%d, y=%d)\n", indent, (unsigned long long)maxsamples_npixels, maxsamples_npixels > 1 ? "s" : "", (unsigned long long)maxsamples, maxsamples_pixel.x, maxsamples_pixel.y); printf ("%sAverage deep samples per pixel: %.2f\n", indent, double(totalsamples)/double(npixels)); printf ("%sTotal deep samples in all pixels: %llu\n", indent, (unsigned long long)totalsamples); printf ("%sPixels with deep samples : %llu\n", indent, (unsigned long long)(npixels-emptypixels)); printf ("%sPixels with no deep samples: %llu\n", indent, (unsigned long long)emptypixels); printf ("%sSamples/pixel histogram:\n", indent); size_t grandtotal = 0; for (size_t i = 0, e = nsamples_histogram.size(); i < e; ++i) grandtotal += nsamples_histogram[i]; size_t binstart = 0, bintotal = 0; for (size_t i = 0, e = nsamples_histogram.size(); i < e; ++i) { bintotal += nsamples_histogram[i]; if (i < 8 || i == (e-1) || OIIO::ispow2(i+1)) { // batch by powers of 2, unless it's a small number if (i == binstart) printf ("%s %3lld ", indent, (long long)i); else printf ("%s %3lld-%3lld", indent, (long long)binstart, (long long)i); printf (" : %8lld (%4.1f%%)\n", (long long)bintotal, (100.0*bintotal)/grandtotal); binstart = i+1; bintotal = 0; } } if (depthchannel >= 0) { printf ("%sMinimum depth was %g at (%d, %d)\n", indent, mindepth, mindepth_pixel.x, mindepth_pixel.y); printf ("%sMaximum depth was %g at (%d, %d)\n", indent, maxdepth, maxdepth_pixel.x, maxdepth_pixel.y); } if (nonfinites > 0) { printf ("%sNonfinite values: %lld, including (x=%d, y=%d, chan=%s, samp=%d)\n", indent, nonfinites, nonfinite_pixel.x, nonfinite_pixel.y, input.spec().channelnames[nonfinite_pixel_chan].c_str(), nonfinite_pixel_samp); } } else { std::vector<float> constantValues(input.spec().nchannels); if (isConstantColor(input, &constantValues[0])) { printf ("%sConstant: Yes\n", indent); printf ("%sConstant Color: ", indent); for (unsigned int i=0; i<constantValues.size(); ++i) { print_stats_num (constantValues[i], maxval, false); printf (" "); } print_stats_footer (maxval); printf ("\n"); } else { printf ("%sConstant: No\n", indent); } if( isMonochrome(input)) { printf ("%sMonochrome: Yes\n", indent); } else { printf ("%sMonochrome: No\n", indent); } } }
static void print_stats (const std::string &filename, const ImageSpec &originalspec, int subimage=0, int miplevel=0, bool indentmip=false) { const char *indent = indentmip ? " " : " "; ImageBuf input; if (! read_input (filename, input, subimage, miplevel)) { std::cerr << "Stats: read error: " << input.geterror() << "\n"; return; } PixelStats stats; if (! computePixelStats (stats, input)) { printf ("%sStats: (unable to compute)\n", indent); return; } // The original spec is used, otherwise the bit depth will // be reported incorrectly (as FLOAT) unsigned int maxval = (unsigned int)get_intsample_maxval (originalspec); printf ("%sStats Min: ", indent); for (unsigned int i=0; i<stats.min.size(); ++i) { print_stats_num (stats.min[i], maxval, true); printf (" "); } print_stats_footer (maxval); printf ("\n"); printf ("%sStats Max: ", indent); for (unsigned int i=0; i<stats.max.size(); ++i) { print_stats_num (stats.max[i], maxval, true); printf (" "); } print_stats_footer (maxval); printf ("\n"); printf ("%sStats Avg: ", indent); for (unsigned int i=0; i<stats.avg.size(); ++i) { print_stats_num (stats.avg[i], maxval, false); printf (" "); } print_stats_footer (maxval); printf ("\n"); printf ("%sStats StdDev: ", indent); for (unsigned int i=0; i<stats.stddev.size(); ++i) { print_stats_num (stats.stddev[i], maxval, false); printf (" "); } print_stats_footer (maxval); printf ("\n"); printf ("%sStats NanCount: ", indent); for (unsigned int i=0; i<stats.nancount.size(); ++i) { printf ("%llu ", (unsigned long long)stats.nancount[i]); } printf ("\n"); printf ("%sStats InfCount: ", indent); for (unsigned int i=0; i<stats.infcount.size(); ++i) { printf ("%llu ", (unsigned long long)stats.infcount[i]); } printf ("\n"); printf ("%sStats FiniteCount: ", indent); for (unsigned int i=0; i<stats.finitecount.size(); ++i) { printf ("%llu ", (unsigned long long)stats.finitecount[i]); } printf ("\n"); if (input.deep()) { const DeepData *dd (input.deepdata()); size_t npixels = dd->nsamples.size(); size_t totalsamples = 0, emptypixels = 0; size_t maxsamples = 0, minsamples = std::numeric_limits<size_t>::max(); for (size_t p = 0; p < npixels; ++p) { size_t c = size_t(dd->nsamples[p]); totalsamples += c; if (c > maxsamples) maxsamples = c; if (c < minsamples) minsamples = c; if (c == 0) ++emptypixels; } printf ("%sMin deep samples in any pixel : %llu\n", indent, (unsigned long long)minsamples); printf ("%sMax deep samples in any pixel : %llu\n", indent, (unsigned long long)maxsamples); printf ("%sAverage deep samples per pixel: %.2f\n", indent, double(totalsamples)/double(npixels)); printf ("%sTotal deep samples in all pixels: %llu\n", indent, (unsigned long long)totalsamples); printf ("%sPixels with deep samples : %llu\n", indent, (unsigned long long)(npixels-emptypixels)); printf ("%sPixels with no deep samples: %llu\n", indent, (unsigned long long)emptypixels); } else { std::vector<float> constantValues(input.spec().nchannels); if (isConstantColor(input, &constantValues[0])) { printf ("%sConstant: Yes\n", indent); printf ("%sConstant Color: ", indent); for (unsigned int i=0; i<constantValues.size(); ++i) { print_stats_num (constantValues[i], maxval, false); printf (" "); } print_stats_footer (maxval); printf ("\n"); } else { printf ("%sConstant: No\n", indent); } if( isMonochrome(input)) { printf ("%sMonochrome: Yes\n", indent); } else { printf ("%sMonochrome: No\n", indent); } } }
static void test_texture3d (ustring filename) { std::cerr << "Testing 3d texture " << filename << ", output = " << output_filename << "\n"; const int nchannels = 4; ImageSpec outspec (output_xres, output_yres, nchannels, TypeDesc::HALF); ImageBuf image (output_filename, outspec); ImageBufAlgo::zero (image); Imath::M33f scale; scale.scale (Imath::V2f (0.5, 0.5)); Imath::M33f rot; rot.rotate (radians(30.0f)); Imath::M33f trans; trans.translate (Imath::V2f (0.35f, 0.15f)); Imath::M33f xform = scale * rot * trans; xform.invert(); TextureOptions opt; opt.sblur = blur; opt.tblur = blur; opt.rblur = blur; opt.swidth = width; opt.twidth = width; opt.rwidth = width; opt.nchannels = nchannels; float localfill = (fill >= 0 ? fill : 0.0f); opt.fill = localfill; if (missing[0] >= 0) opt.missingcolor.init ((float *)&missing, 0); opt.swrap = opt.twrap = opt.rwrap = TextureOptions::WrapPeriodic; int shadepoints = blocksize*blocksize; Imath::V3f *P = ALLOCA (Imath::V3f, shadepoints); Runflag *runflags = ALLOCA (Runflag, shadepoints); Imath::V3f *dPdx = ALLOCA (Imath::V3f, shadepoints); Imath::V3f *dPdy = ALLOCA (Imath::V3f, shadepoints); Imath::V3f *dPdz = ALLOCA (Imath::V3f, shadepoints); float *result = ALLOCA (float, shadepoints*nchannels); for (int iter = 0; iter < iters; ++iter) { // Iterate over blocks // Trick: switch to second texture, if given, for second iteration if (iter && filenames.size() > 1) filename = ustring (filenames[1]); for (int by = 0; by < output_yres; by+=blocksize) { for (int bx = 0; bx < output_xres; bx+=blocksize) { // Process pixels within a block. First save the texture warp // (s,t) and derivatives into SIMD vectors. int idx = 0; for (int y = by; y < by+blocksize; ++y) { for (int x = bx; x < bx+blocksize; ++x) { if (x < output_xres && y < output_yres) { if (nowarp) { P[idx][0] = (float)x/output_xres * sscale; P[idx][1] = (float)y/output_yres * tscale; P[idx][2] = 0.5f * sscale; P[idx] += offset; dPdx[idx][0] = 1.0f/output_xres * sscale; dPdx[idx][1] = 0; dPdx[idx][2] = 0; dPdy[idx][0] = 0; dPdy[idx][1] = 1.0f/output_yres * tscale; dPdy[idx][2] = 0; dPdz[idx].setValue (0,0,0); } else { Imath::V3f coord = warp ((float)x/output_xres, (float)y/output_yres, 0.5, xform); coord.x *= sscale; coord.y *= tscale; coord += offset; Imath::V3f coordx = warp ((float)(x+1)/output_xres, (float)y/output_yres, 0.5, xform); coordx.x *= sscale; coordx.y *= tscale; coordx += offset; Imath::V3f coordy = warp ((float)x/output_xres, (float)(y+1)/output_yres, 0.5, xform); coordy.x *= sscale; coordy.y *= tscale; coordy += offset; P[idx] = coord; dPdx[idx] = coordx - coord; dPdy[idx] = coordy - coord; dPdz[idx].setValue (0,0,0); } runflags[idx] = RunFlagOn; } else { runflags[idx] = RunFlagOff; } ++idx; } } // Call the texture system to do the filtering. bool ok = texsys->texture3d (filename, opt, runflags, 0, shadepoints, Varying(P), Varying(dPdx), Varying(dPdy), Varying(dPdz), result); if (! ok) { std::string e = texsys->geterror (); if (! e.empty()) std::cerr << "ERROR: " << e << "\n"; } for (int i = 0; i < shadepoints*nchannels; ++i) result[i] *= scalefactor; // Save filtered pixels back to the image. idx = 0; for (int y = by; y < by+blocksize; ++y) { for (int x = bx; x < bx+blocksize; ++x) { if (runflags[idx]) { image.setpixel (x, y, result + idx*nchannels); } ++idx; } } } } } if (! image.save ()) std::cerr << "Error writing " << output_filename << " : " << image.geterror() << "\n"; }
static void test_plain_texture () { std::cerr << "Testing 2d texture " << filenames[0] << ", output = " << output_filename << "\n"; const int nchannels = 4; ImageSpec outspec (output_xres, output_yres, nchannels, TypeDesc::HALF); ImageBuf image (output_filename, outspec); ImageBufAlgo::zero (image); Imath::M33f scale; scale.scale (Imath::V2f (0.5, 0.5)); Imath::M33f rot; rot.rotate (radians(30.0f)); Imath::M33f trans; trans.translate (Imath::V2f (0.35f, 0.15f)); Imath::M33f xform = scale * rot * trans; xform.invert(); TextureOptions opt; opt.sblur = blur; opt.tblur = blur; opt.swidth = width; opt.twidth = width; opt.nchannels = nchannels; float localfill = (fill >= 0.0f) ? fill : 1.0f; opt.fill = localfill; if (missing[0] >= 0) opt.missingcolor.init ((float *)&missing, 0); // opt.interpmode = TextureOptions::InterpSmartBicubic; // opt.mipmode = TextureOptions::MipModeAniso; opt.swrap = opt.twrap = TextureOptions::WrapPeriodic; // opt.twrap = TextureOptions::WrapBlack; #if 1 TextureOpt opt1; opt1.sblur = blur; opt1.tblur = blur; opt1.swidth = width; opt1.twidth = width; opt1.nchannels = nchannels; opt1.fill = localfill; if (missing[0] >= 0) opt1.missingcolor = (float *)&missing; opt1.swrap = opt1.twrap = TextureOpt::WrapPeriodic; #endif int shadepoints = blocksize*blocksize; float *s = ALLOCA (float, shadepoints); float *t = ALLOCA (float, shadepoints); Runflag *runflags = ALLOCA (Runflag, shadepoints); float *dsdx = ALLOCA (float, shadepoints); float *dtdx = ALLOCA (float, shadepoints); float *dsdy = ALLOCA (float, shadepoints); float *dtdy = ALLOCA (float, shadepoints); float *result = ALLOCA (float, shadepoints*nchannels); ustring filename = ustring (filenames[0]); TextureSystem::Perthread *perthread_info = texsys->get_perthread_info (); TextureSystem::TextureHandle *texture_handle = texsys->get_texture_handle (filename); for (int iter = 0; iter < iters; ++iter) { if (iters > 1 && filenames.size() > 1) { // Use a different filename for each iteration int texid = std::min (iter, (int)filenames.size()-1); filename = ustring (filenames[texid]); std::cerr << "iter " << iter << " file " << filename << "\n"; } // Iterate over blocks for (int by = 0, b = 0; by < output_yres; by+=blocksize) { for (int bx = 0; bx < output_xres; bx+=blocksize, ++b) { // Trick: switch to other textures on later iterations, if any if (iters == 1 && filenames.size() > 1) { // Use a different filename from block to block filename = ustring (filenames[b % (int)filenames.size()]); } // Process pixels within a block. First save the texture warp // (s,t) and derivatives into SIMD vectors. int idx = 0; for (int y = by; y < by+blocksize; ++y) { for (int x = bx; x < bx+blocksize; ++x) { if (x < output_xres && y < output_yres) { if (nowarp) { s[idx] = (float)x/output_xres * sscale + offset[0]; t[idx] = (float)y/output_yres * tscale + offset[1]; dsdx[idx] = 1.0f/output_xres * sscale; dtdx[idx] = 0; dsdy[idx] = 0; dtdy[idx] = 1.0f/output_yres * tscale; } else { Imath::V3f coord = warp ((float)x/output_xres, (float)y/output_yres, xform); coord.x *= sscale; coord.y *= tscale; coord += offset; Imath::V3f coordx = warp ((float)(x+1)/output_xres, (float)y/output_yres, xform); coordx.x *= sscale; coordx.y *= tscale; coordx += offset; Imath::V3f coordy = warp ((float)x/output_xres, (float)(y+1)/output_yres, xform); coordy.x *= sscale; coordy.y *= tscale; coordy += offset; s[idx] = coord[0]; t[idx] = coord[1]; dsdx[idx] = coordx[0] - coord[0]; dtdx[idx] = coordx[1] - coord[1]; dsdy[idx] = coordy[0] - coord[0]; dtdy[idx] = coordy[1] - coord[1]; } runflags[idx] = RunFlagOn; } else { runflags[idx] = RunFlagOff; } ++idx; } } // Call the texture system to do the filtering. bool ok; if (blocksize == 1) { if (use_handle) ok = texsys->texture (texture_handle, perthread_info, opt1, s[0], t[0], dsdx[0], dtdx[0], dsdy[0], dtdy[0], result); else ok = texsys->texture (filename, opt1, s[0], t[0], dsdx[0], dtdx[0], dsdy[0], dtdy[0], result); } else { ok = texsys->texture (filename, opt, runflags, 0, shadepoints, Varying(s), Varying(t), Varying(dsdx), Varying(dtdx), Varying(dsdy), Varying(dtdy), result); } if (! ok) { std::string e = texsys->geterror (); if (! e.empty()) std::cerr << "ERROR: " << e << "\n"; } for (int i = 0; i < shadepoints*nchannels; ++i) result[i] *= scalefactor; // Save filtered pixels back to the image. idx = 0; for (int y = by; y < by+blocksize; ++y) { for (int x = bx; x < bx+blocksize; ++x) { if (runflags[idx]) { image.setpixel (x, y, result + idx*nchannels); } ++idx; } } } } } if (! image.save ()) std::cerr << "Error writing " << output_filename << " : " << image.geterror() << "\n"; }
static void print_stats (const std::string &filename, const ImageSpec &originalspec, int subimage=0, int miplevel=0, bool indentmip=false) { const char *indent = indentmip ? " " : " "; ImageBuf input; if (! read_input (filename, input, subimage, miplevel)) { std::cerr << "Stats: read error: " << input.geterror() << "\n"; return; } PixelStats stats; if (! computePixelStats (stats, input)) { printf ("%sStats: (unable to compute)\n", indent); return; } // The original spec is used, otherwise the bit depth will // be reported incorrectly (as FLOAT) unsigned int maxval = (unsigned int)get_intsample_maxval (originalspec); printf ("%sStats Min: ", indent); for (unsigned int i=0; i<stats.min.size(); ++i) { print_stats_num (stats.min[i], maxval, true); printf (" "); } print_stats_footer (maxval); printf ("\n"); printf ("%sStats Max: ", indent); for (unsigned int i=0; i<stats.max.size(); ++i) { print_stats_num (stats.max[i], maxval, true); printf (" "); } print_stats_footer (maxval); printf ("\n"); printf ("%sStats Avg: ", indent); for (unsigned int i=0; i<stats.avg.size(); ++i) { print_stats_num (stats.avg[i], maxval, false); printf (" "); } print_stats_footer (maxval); printf ("\n"); printf ("%sStats StdDev: ", indent); for (unsigned int i=0; i<stats.stddev.size(); ++i) { print_stats_num (stats.stddev[i], maxval, false); printf (" "); } print_stats_footer (maxval); printf ("\n"); printf ("%sStats NanCount: ", indent); for (unsigned int i=0; i<stats.nancount.size(); ++i) { printf ("%llu ", (unsigned long long)stats.nancount[i]); } printf ("\n"); printf ("%sStats InfCount: ", indent); for (unsigned int i=0; i<stats.infcount.size(); ++i) { printf ("%llu ", (unsigned long long)stats.infcount[i]); } printf ("\n"); printf ("%sStats FiniteCount: ", indent); for (unsigned int i=0; i<stats.finitecount.size(); ++i) { printf ("%llu ", (unsigned long long)stats.finitecount[i]); } printf ("\n"); std::vector<float> constantValues(input.spec().nchannels); if(isConstantColor(input, &constantValues[0])) { printf ("%sConstant: Yes\n", indent); printf ("%sConstant Color: ", indent); for (unsigned int i=0; i<constantValues.size(); ++i) { print_stats_num (constantValues[i], maxval, false); printf (" "); } print_stats_footer (maxval); printf ("\n"); } else { printf ("%sConstant: No\n", indent); } if(isMonochrome(input)) { printf ("%sMonochrome: Yes\n", indent); } else { printf ("%sMonochrome: No\n", indent); } }
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); }
void SMT::pasteDecals(ImageBuf *bigBuf) { printf("WARNING: decal pasting is not yet implemented\n"); return; // load definitions file vector<type> list; // Parse the file printf( "INFO: Reading %s\n", decalFile.c_str() ); ifstream decalList( decalFile.c_str() ); string line; string cell; vector<string> tokens; while ( getline( decalList, line ) ) { stringstream lineStream( line ); tokens.clear(); while( getline( lineStream, cell, ',' ) ) tokens.push_back( cell ); if(tokens.size() <2 ) continue; // use parsed line coord xy; type *decal; // search for existing decal filename bool found = false; for(unsigned int i = 0; i < list.size(); ++i ) { if( !list[i].imageName.compare( tokens[0].c_str() ) ) { decal = &list[i]; found = true; break; } } if( !found ) { decal = new type; decal->imageName = tokens[0].c_str(); } xy.x = atoi( tokens[1].c_str() ); xy.y = atoi( tokens[2].c_str() ); decal->coordinates.push_back(xy); if( !found ) list.push_back(*decal); } // loop through list of decals. for(unsigned int i = 0; i < list.size(); ++i ) { if( verbose )printf(" %i %s ", (int)list[i].coordinates.size(), list[i].imageName.c_str() ); ImageBuf decalBuf( list[i].imageName ); decalBuf.read( 0, 0, false, TypeDesc::FLOAT ); if( !decalBuf.initialized() ) { printf("ERROR: could not load %s\n", list[i].imageName.c_str() ); continue; } ImageSpec decalSpec = decalBuf.spec(); printf("(%i,%i)%i\n", decalSpec.width, decalSpec.height, decalSpec.nchannels ); // loop through list of coordinates for(unsigned int j = 0; j < list[i].coordinates.size(); ++j ) { coord xy = list[i].coordinates[j]; ROI roi( xy.x, xy.x + decalSpec.width, xy.y, xy.y + decalSpec.height, 0,1, 0,5); ImageBuf pasteBuf; ImageBufAlgo::crop(pasteBuf, *bigBuf, roi); ImageBuf compBuf; if( !ImageBufAlgo::over( compBuf, decalBuf, pasteBuf) ) { cout << "ERROR with composite: "; cout << compBuf.geterror() << endl; continue; } char filename[256]; sprintf(filename, "test%i.png", j); compBuf.save(filename); } } return; }
int main (int argc, char *argv[]) { // Arguments if(argc != 3) { std::cerr << "mkthumbnail usage : mkthumbnail <input> <output>" << std::endl; return EXIT_FAILURE; } std::string inputname=argv[1]; std::string outputname=argv[2]; std::string colortransfer_to = "sRGB"; std::string colortransfer_from = "sRGB"; // Colorspace selection if(extensionIs(inputname, "exr") ) { colortransfer_from="Linear"; } else if(extensionIs(inputname, "dpx")) { colortransfer_from="KodakLog"; } else { colortransfer_from="sRGB"; } bool ok = true; ImageBuf in; if (! read_input (inputname, in)) { std::cerr << "mkthumbnail: read error: " << in.geterror() << "\n"; return EXIT_FAILURE; } ColorTransfer *from_func = ColorTransfer::create (colortransfer_from + "_to_linear"); if (from_func == NULL) { std::cerr << "mkthumbnail: --colorspace needs a 'colorspace' of " << "Linear, Gamma, sRGB, AdobeRGB, Rec709 or KodakLog\n"; return EXIT_FAILURE; } ColorTransfer *to_func = ColorTransfer::create (std::string("linear_to_") + colortransfer_to); if (to_func == NULL) { std::cerr << "mkthumbnail: --transfer needs a 'colorspace' of " << "Linear, Gamma, sRGB, AdobeRGB, Rec709 or KodakLog\n"; return EXIT_FAILURE; } std::cout << "Converting [" << colortransfer_from << "] " << inputname << " to [" << colortransfer_to << "] " << outputname << "\n"; // ImageBuf linear; ImageBuf outtmp; ImageBufAlgo::colortransfer (linear, in, from_func); ImageBufAlgo::colortransfer (outtmp, linear, to_func); std::cout << "finished color transfer\n"; // Pixel aspect ratio const ImageIOParameter *aspect = in.spec().find_attribute ("pixelaspectratio", TypeDesc::FLOAT); float pixel_ratio = aspect ? *(float *)aspect->data() : 1.0f; float ratio = ((float)in.spec().height+(float)in.spec().y)/((float)in.spec().width+(float)in.spec().x); pixel_ratio = pixel_ratio != 0.f ? pixel_ratio : 1.f; const int resize_w = 480; // resize target width int resize_y = (int)((float)resize_w*ratio/pixel_ratio); int resize_x = resize_w; ImageSpec outspec = outtmp.spec(); outspec.x = 0; outspec.y = 0; outspec.full_x = 0; outspec.full_y = 0; outspec.width = resize_x; outspec.height = resize_y; outspec.full_width = resize_x; outspec.full_height = resize_y; ImageBuf out (outputname, outspec); float pixel[3] = { .1f, .1f, .1f }; ImageBufAlgo::fill (out, pixel); if(!ImageBufAlgo::resize (out, outtmp, out.xbegin(), out.xend(), out.ybegin(), out.yend())) { std::cerr << "mkthumbnail: unable to resize image, " << out.geterror() << std::endl; return EXIT_FAILURE; } if(out.spec().nchannels>3) { std::cout << "Changing number of channels to 3 "<< "\n"; ImageBuf newout("temp", out.spec()); setNbChannels (newout, out, 3); out = newout; } if(!out.save (outputname)) { std::cerr << "mkthumbnail: unable to save output image, " << out.geterror() << std::endl; return EXIT_FAILURE; } return EXIT_SUCCESS; }
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; }
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; }
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; }
static void test_plain_texture () { std::cerr << "Testing 2d texture " << filenames[0] << ", output = " << output_filename << "\n"; const int nchannels = 4; ImageSpec outspec (output_xres, output_yres, nchannels, TypeDesc::HALF); if (! dataformatname.empty()) { if (dataformatname == "uint8") outspec.set_format (TypeDesc::UINT8); else if (dataformatname == "int8") outspec.set_format (TypeDesc::INT8); else if (dataformatname == "uint10") { outspec.attribute ("oiio:BitsPerSample", 10); outspec.set_format (TypeDesc::UINT16); } else if (dataformatname == "uint12") { outspec.attribute ("oiio:BitsPerSample", 12); outspec.set_format (TypeDesc::UINT16); } else if (dataformatname == "uint16") outspec.set_format (TypeDesc::UINT16); else if (dataformatname == "int16") outspec.set_format (TypeDesc::INT16); else if (dataformatname == "half") outspec.set_format (TypeDesc::HALF); else if (dataformatname == "float") outspec.set_format (TypeDesc::FLOAT); else if (dataformatname == "double") outspec.set_format (TypeDesc::DOUBLE); outspec.channelformats.clear (); } ImageBuf image (output_filename, outspec); ImageBufAlgo::zero (image); Imath::M33f scale; scale.scale (Imath::V2f (0.5, 0.5)); Imath::M33f rot; rot.rotate (radians(30.0f)); Imath::M33f trans; trans.translate (Imath::V2f (0.35f, 0.15f)); Imath::M33f xform = scale * rot * trans; xform.invert(); TextureOptions opt; opt.sblur = blur; opt.tblur = blur; opt.swidth = width; opt.twidth = width; opt.nchannels = nchannels; float localfill = (fill >= 0.0f) ? fill : 1.0f; opt.fill = localfill; if (missing[0] >= 0) opt.missingcolor.init ((float *)&missing, 0); // opt.interpmode = TextureOptions::InterpSmartBicubic; // opt.mipmode = TextureOptions::MipModeAniso; TextureOptions::parse_wrapmodes (wrapmodes.c_str(), opt.swrap, opt.twrap); TextureOpt opt1; opt1.sblur = blur; opt1.tblur = blur; opt1.swidth = width; opt1.twidth = width; opt1.nchannels = nchannels; opt1.fill = localfill; if (missing[0] >= 0) opt1.missingcolor = (float *)&missing; TextureOpt::parse_wrapmodes (wrapmodes.c_str(), opt1.swrap, opt1.twrap); int shadepoints = blocksize*blocksize; float *s = ALLOCA (float, shadepoints); float *t = ALLOCA (float, shadepoints); Runflag *runflags = ALLOCA (Runflag, shadepoints); float *dsdx = ALLOCA (float, shadepoints); float *dtdx = ALLOCA (float, shadepoints); float *dsdy = ALLOCA (float, shadepoints); float *dtdy = ALLOCA (float, shadepoints); float *result = ALLOCA (float, shadepoints*nchannels); ustring filename = ustring (filenames[0]); TextureSystem::Perthread *perthread_info = texsys->get_perthread_info (); TextureSystem::TextureHandle *texture_handle = texsys->get_texture_handle (filename); for (int iter = 0; iter < iters; ++iter) { if (iters > 1 && filenames.size() > 1) { // Use a different filename for each iteration int texid = std::min (iter, (int)filenames.size()-1); filename = ustring (filenames[texid]); std::cerr << "iter " << iter << " file " << filename << "\n"; } // Iterate over blocks for (int by = 0, b = 0; by < output_yres; by+=blocksize) { for (int bx = 0; bx < output_xres; bx+=blocksize, ++b) { // Trick: switch to other textures on later iterations, if any if (iters == 1 && filenames.size() > 1) { // Use a different filename from block to block filename = ustring (filenames[b % (int)filenames.size()]); } // Process pixels within a block. First save the texture warp // (s,t) and derivatives into SIMD vectors. int idx = 0; for (int y = by; y < by+blocksize; ++y) { for (int x = bx; x < bx+blocksize; ++x) { if (x < output_xres && y < output_yres) { if (nowarp) { s[idx] = (float)x/output_xres * sscale + offset[0]; t[idx] = (float)y/output_yres * tscale + offset[1]; dsdx[idx] = 1.0f/output_xres * sscale; dtdx[idx] = 0; dsdy[idx] = 0; dtdy[idx] = 1.0f/output_yres * tscale; } else if (tube) { float xt = float(x)/output_xres - 0.5f; float dxt_dx = 1.0f/output_xres; float yt = float(y)/output_yres - 0.5f; float dyt_dy = 1.0f/output_yres; float theta = atan2f (yt, xt); // See OSL's Dual2 for partial derivs of // atan2, hypot, and 1/x float denom = 1.0f / (xt*xt + yt*yt); float dtheta_dx = yt*dxt_dx * denom; float dtheta_dy = -xt*dyt_dy * denom; s[idx] = 4.0f * theta / (2.0f * M_PI); dsdx[idx] = 4.0f * dtheta_dx / (2.0f * M_PI); dsdy[idx] = 4.0f * dtheta_dy / (2.0f * M_PI); float h = hypot(xt,yt); float dh_dx = xt*dxt_dx / h; float dh_dy = yt*dyt_dy / h; h *= M_SQRT2; dh_dx *= M_SQRT2; dh_dy *= M_SQRT2; float hinv = 1.0f / h; t[idx] = hinv; dtdx[idx] = hinv * (-hinv * dh_dx); dtdy[idx] = hinv * (-hinv * dh_dy); } else { Imath::V3f coord = warp ((float)x/output_xres, (float)y/output_yres, xform); coord.x *= sscale; coord.y *= tscale; coord += offset; Imath::V3f coordx = warp ((float)(x+1)/output_xres, (float)y/output_yres, xform); coordx.x *= sscale; coordx.y *= tscale; coordx += offset; Imath::V3f coordy = warp ((float)x/output_xres, (float)(y+1)/output_yres, xform); coordy.x *= sscale; coordy.y *= tscale; coordy += offset; s[idx] = coord[0]; t[idx] = coord[1]; dsdx[idx] = coordx[0] - coord[0]; dtdx[idx] = coordx[1] - coord[1]; dsdy[idx] = coordy[0] - coord[0]; dtdy[idx] = coordy[1] - coord[1]; } runflags[idx] = RunFlagOn; } else { runflags[idx] = RunFlagOff; } ++idx; } } // Call the texture system to do the filtering. bool ok; if (blocksize == 1) { if (use_handle) ok = texsys->texture (texture_handle, perthread_info, opt1, s[0], t[0], dsdx[0], dtdx[0], dsdy[0], dtdy[0], result); else ok = texsys->texture (filename, opt1, s[0], t[0], dsdx[0], dtdx[0], dsdy[0], dtdy[0], result); } else { ok = texsys->texture (filename, opt, runflags, 0, shadepoints, Varying(s), Varying(t), Varying(dsdx), Varying(dtdx), Varying(dsdy), Varying(dtdy), result); } if (! ok) { std::string e = texsys->geterror (); if (! e.empty()) std::cerr << "ERROR: " << e << "\n"; } for (int i = 0; i < shadepoints*nchannels; ++i) result[i] *= scalefactor; // Save filtered pixels back to the image. idx = 0; for (int y = by; y < by+blocksize; ++y) { for (int x = bx; x < bx+blocksize; ++x) { if (runflags[idx]) { image.setpixel (x, y, result + idx*nchannels); } ++idx; } } } } if (resetstats) { std::cout << texsys->getstats(2) << "\n"; texsys->reset_stats (); } } if (! image.save ()) std::cerr << "Error writing " << output_filename << " : " << image.geterror() << "\n"; }
static void print_stats (const std::string &filename, const ImageSpec &originalspec, int subimage=0, int miplevel=0, bool indentmip=false) { const char *indent = indentmip ? " " : " "; ImageBuf input; if (! read_input (filename, input, subimage, miplevel)) { std::cerr << "Stats: read error: " << input.geterror() << "\n"; return; } PixelStats stats; if (! computePixelStats (stats, input)) { printf ("%sStats: (unable to compute)\n", indent); return; } // The original spec is used, otherwise the bit depth will // be reported incorrectly (as FLOAT) unsigned int maxval = (unsigned int)get_intsample_maxval (originalspec); printf ("%sStats Min: ", indent); for (unsigned int i=0; i<stats.min.size(); ++i) { print_stats_num (stats.min[i], maxval, true); printf (" "); } print_stats_footer (maxval); printf ("\n"); printf ("%sStats Max: ", indent); for (unsigned int i=0; i<stats.max.size(); ++i) { print_stats_num (stats.max[i], maxval, true); printf (" "); } print_stats_footer (maxval); printf ("\n"); printf ("%sStats Avg: ", indent); for (unsigned int i=0; i<stats.avg.size(); ++i) { print_stats_num (stats.avg[i], maxval, false); printf (" "); } print_stats_footer (maxval); printf ("\n"); printf ("%sStats StdDev: ", indent); for (unsigned int i=0; i<stats.stddev.size(); ++i) { print_stats_num (stats.stddev[i], maxval, false); printf (" "); } print_stats_footer (maxval); printf ("\n"); printf ("%sStats NanCount: ", indent); for (unsigned int i=0; i<stats.nancount.size(); ++i) { printf ("%llu ", (unsigned long long)stats.nancount[i]); } printf ("\n"); printf ("%sStats InfCount: ", indent); for (unsigned int i=0; i<stats.infcount.size(); ++i) { printf ("%llu ", (unsigned long long)stats.infcount[i]); } printf ("\n"); printf ("%sStats FiniteCount: ", indent); for (unsigned int i=0; i<stats.finitecount.size(); ++i) { printf ("%llu ", (unsigned long long)stats.finitecount[i]); } printf ("\n"); if (input.deep()) { const DeepData *dd (input.deepdata()); size_t npixels = dd->nsamples.size(); size_t totalsamples = 0, emptypixels = 0; size_t maxsamples = 0, minsamples = std::numeric_limits<size_t>::max(); size_t maxsamples_npixels = 0; float mindepth = std::numeric_limits<float>::max(); float maxdepth = -std::numeric_limits<float>::max(); Imath::V3i maxsamples_pixel(-1,-1,-1), minsamples_pixel(-1,-1,-1); Imath::V3i mindepth_pixel(-1,-1,-1), maxdepth_pixel(-1,-1,-1); size_t sampoffset = 0; int depthchannel = -1; for (int c = 0; c < input.nchannels(); ++c) if (Strutil::iequals (originalspec.channelnames[c], "Z")) depthchannel = c; int xend = originalspec.x + originalspec.width; int yend = originalspec.y + originalspec.height; int zend = originalspec.z + originalspec.depth; size_t p = 0; for (int z = originalspec.z; z < zend; ++z) { for (int y = originalspec.y; y < yend; ++y) { for (int x = originalspec.x; x < xend; ++x, ++p) { size_t c = input.deep_samples (x, y, z); totalsamples += c; if (c == maxsamples) ++maxsamples_npixels; if (c > maxsamples) { maxsamples = c; maxsamples_pixel.setValue (x, y, z); maxsamples_npixels = 1; } if (c < minsamples) minsamples = c; if (c == 0) ++emptypixels; if (depthchannel >= 0) { for (unsigned int s = 0; s < c; ++s) { float d = input.deep_value (x, y, z, depthchannel, s); if (d < mindepth) { mindepth = d; mindepth_pixel.setValue (x, y, z); } if (d > maxdepth) { maxdepth = d; maxdepth_pixel.setValue (x, y, z); } } } sampoffset += c; } } } printf ("%sMin deep samples in any pixel : %llu\n", indent, (unsigned long long)minsamples); printf ("%sMax deep samples in any pixel : %llu\n", indent, (unsigned long long)maxsamples); printf ("%s%llu pixel%s had the max of %llu samples, including (x=%d, y=%d)\n", indent, (unsigned long long)maxsamples_npixels, maxsamples_npixels > 1 ? "s" : "", (unsigned long long)maxsamples, maxsamples_pixel.x, maxsamples_pixel.y); printf ("%sAverage deep samples per pixel: %.2f\n", indent, double(totalsamples)/double(npixels)); printf ("%sTotal deep samples in all pixels: %llu\n", indent, (unsigned long long)totalsamples); printf ("%sPixels with deep samples : %llu\n", indent, (unsigned long long)(npixels-emptypixels)); printf ("%sPixels with no deep samples: %llu\n", indent, (unsigned long long)emptypixels); printf ("%sMinimum depth was %g at (%d, %d)\n", indent, mindepth, mindepth_pixel.x, mindepth_pixel.y); printf ("%sMaximum depth was %g at (%d, %d)\n", indent, maxdepth, maxdepth_pixel.x, maxdepth_pixel.y); } else { std::vector<float> constantValues(input.spec().nchannels); if (isConstantColor(input, &constantValues[0])) { printf ("%sConstant: Yes\n", indent); printf ("%sConstant Color: ", indent); for (unsigned int i=0; i<constantValues.size(); ++i) { print_stats_num (constantValues[i], maxval, false); printf (" "); } print_stats_footer (maxval); printf ("\n"); } else { printf ("%sConstant: No\n", indent); } if( isMonochrome(input)) { printf ("%sMonochrome: Yes\n", indent); } else { printf ("%sMonochrome: No\n", indent); } } }