static bool paste_ (ImageBuf &dst, ROI dstroi, const ImageBuf &src, ROI srcroi, int nthreads) { // N.B. Punt on parallelizing because of the subtle interplay // between srcroi and dstroi, the parallel_image idiom doesn't // handle that especially well. And it's not worth customizing for // this function which is inexpensive and not commonly used, and so // would benefit little from parallelizing. We can always revisit // this later. But in the mean time, we maintain the 'nthreads' // parameter for uniformity with the rest of IBA. int src_nchans = src.nchannels (); int dst_nchans = dst.nchannels (); ImageBuf::ConstIterator<S,D> s (src, srcroi); ImageBuf::Iterator<D,D> d (dst, dstroi); for ( ; ! s.done(); ++s, ++d) { if (! d.exists()) continue; // Skip paste-into pixels that don't overlap dst's data for (int c = srcroi.chbegin, c_dst = dstroi.chbegin; c < srcroi.chend; ++c, ++c_dst) { if (c_dst >= 0 && c_dst < dst_nchans) d[c_dst] = c < src_nchans ? s[c] : D(0); } } return true; }
bool ImageBufAlgo::compare (const ImageBuf &A, const ImageBuf &B, float failthresh, float warnthresh, ImageBufAlgo::CompareResults &result, ROI roi, int nthreads) { // If no ROI is defined, use the union of the data windows of the two // images. if (! roi.defined()) roi = roi_union (get_roi(A.spec()), get_roi(B.spec())); roi.chend = std::min (roi.chend, std::max(A.nchannels(), B.nchannels())); // Deep and non-deep images cannot be compared if (B.deep() != A.deep()) return false; bool ok; OIIO_DISPATCH_TYPES2 (ok, "compare", compare_, A.spec().format, B.spec().format, A, B, failthresh, warnthresh, result, roi, nthreads); // FIXME - The nthreads argument is for symmetry with the rest of // ImageBufAlgo and for future expansion. But for right now, we // don't actually split by threads. Maybe later. return ok; }
static bool compare_ (const ImageBuf &A, const ImageBuf &B, float failthresh, float warnthresh, ImageBufAlgo::CompareResults &result, ROI roi, int nthreads) { imagesize_t npels = roi.npixels(); imagesize_t nvals = npels * roi.nchannels(); int Achannels = A.nchannels(), Bchannels = B.nchannels(); // Compare the two images. // double totalerror = 0; double totalsqrerror = 0; result.maxerror = 0; result.maxx=0, result.maxy=0, result.maxz=0, result.maxc=0; result.nfail = 0, result.nwarn = 0; float maxval = 1.0; // max possible value ImageBuf::ConstIterator<Atype> a (A, roi, ImageBuf::WrapBlack); ImageBuf::ConstIterator<Btype> b (B, roi, ImageBuf::WrapBlack); bool deep = A.deep(); // Break up into batches to reduce cancelation errors as the error // sums become too much larger than the error for individual pixels. const int batchsize = 4096; // As good a guess as any for ( ; ! a.done(); ) { double batcherror = 0; double batch_sqrerror = 0; if (deep) { for (int i = 0; i < batchsize && !a.done(); ++i, ++a, ++b) { bool warned = false, failed = false; // For this pixel for (int c = roi.chbegin; c < roi.chend; ++c) for (int s = 0, e = a.deep_samples(); s < e; ++s) { compare_value (a, c, a.deep_value(c,s), b.deep_value(c,s), result, maxval, batcherror, batch_sqrerror, failed, warned, failthresh, warnthresh); } } } else { // non-deep for (int i = 0; i < batchsize && !a.done(); ++i, ++a, ++b) { bool warned = false, failed = false; // For this pixel for (int c = roi.chbegin; c < roi.chend; ++c) compare_value (a, c, c < Achannels ? a[c] : 0.0f, c < Bchannels ? b[c] : 0.0f, result, maxval, batcherror, batch_sqrerror, failed, warned, failthresh, warnthresh); } } totalerror += batcherror; totalsqrerror += batch_sqrerror; } result.meanerror = totalerror / nvals; result.rms_error = sqrt (totalsqrerror / nvals); result.PSNR = 20.0 * log10 (maxval / result.rms_error); return result.nfail == 0; }
static inline void copy_pixels_ (const ImageBuf &buf, int xbegin, int xend, int ybegin, int yend, D *r) { int w = (xend-xbegin); for (ImageBuf::ConstIterator<S,D> p (buf, xbegin, xend, ybegin, yend); p.valid(); ++p) { imagesize_t offset = ((p.y()-ybegin)*w + (p.x()-xbegin)) * buf.nchannels(); for (int c = 0; c < buf.nchannels(); ++c) r[offset+c] = p[c]; } }
bool ImageBufAlgo::sub(ImageBuf& dst, Image_or_Const A_, Image_or_Const B_, ROI roi, int nthreads) { pvt::LoggedTimer logtime("IBA::sub"); if (A_.is_img() && B_.is_img()) { const ImageBuf &A(A_.img()), &B(B_.img()); if (!IBAprep(roi, &dst, &A, &B)) return false; ROI origroi = roi; roi.chend = std::min(roi.chend, std::min(A.nchannels(), B.nchannels())); bool ok; OIIO_DISPATCH_COMMON_TYPES3(ok, "sub", sub_impl, dst.spec().format, A.spec().format, B.spec().format, dst, A, B, roi, nthreads); if (roi.chend < origroi.chend && A.nchannels() != B.nchannels()) { // Edge case: A and B differed in nchannels, we allocated dst to be // the bigger of them, but adjusted roi to be the lesser. Now handle // the channels that got left out because they were not common to // all the inputs. ASSERT(roi.chend <= dst.nchannels()); roi.chbegin = roi.chend; roi.chend = origroi.chend; if (A.nchannels() > B.nchannels()) { // A exists copy(dst, A, dst.spec().format, roi, nthreads); } else { // B exists copy(dst, B, dst.spec().format, roi, nthreads); } } return ok; } if (A_.is_val() && B_.is_img()) // canonicalize to A_img, B_val A_.swap(B_); if (A_.is_img() && B_.is_val()) { const ImageBuf& A(A_.img()); cspan<float> b = B_.val(); if (!IBAprep(roi, &dst, &A, IBAprep_CLAMP_MUTUAL_NCHANNELS | IBAprep_SUPPORT_DEEP)) return false; IBA_FIX_PERCHAN_LEN_DEF(b, A.nchannels()); // Negate b (into a copy) int nc = A.nchannels(); float* vals = ALLOCA(float, nc); for (int c = 0; c < nc; ++c) vals[c] = -b[c]; b = cspan<float>(vals, nc); if (dst.deep()) { // While still serial, set up all the sample counts dst.deepdata()->set_all_samples(A.deepdata()->all_samples()); return add_impl_deep(dst, A, b, roi, nthreads); } bool ok; OIIO_DISPATCH_COMMON_TYPES2(ok, "sub", add_impl, dst.spec().format, A.spec().format, dst, A, b, roi, nthreads); return ok; } // Remaining cases: error dst.error("ImageBufAlgo::sub(): at least one argument must be an image"); return false; }
bool ImageBufAlgo::transpose (ImageBuf &dst, const ImageBuf &src, ROI roi, int nthreads) { pvt::LoggedTimer logtime("IBA::transpose"); if (! roi.defined()) roi = get_roi (src.spec()); roi.chend = std::min (roi.chend, src.nchannels()); ROI dst_roi (roi.ybegin, roi.yend, roi.xbegin, roi.xend, roi.zbegin, roi.zend, roi.chbegin, roi.chend); bool dst_initialized = dst.initialized(); if (! IBAprep (dst_roi, &dst)) return false; if (! dst_initialized) { ROI r = src.roi_full(); ROI dst_roi_full (r.ybegin, r.yend, r.xbegin, r.xend, r.zbegin, r.zend, r.chbegin, r.chend); dst.set_roi_full (dst_roi_full); } bool ok; if (dst.spec().format == src.spec().format) { OIIO_DISPATCH_TYPES (ok, "transpose", transpose_, dst.spec().format, dst, src, roi, nthreads); } else { OIIO_DISPATCH_COMMON_TYPES2 (ok, "transpose", transpose_, dst.spec().format, src.spec().format, dst, src, roi, nthreads); } return ok; }
static inline bool isConstantColor_ (const ImageBuf &src, float *color, ROI roi, int nthreads) { // Iterate using the native typing (for speed). std::vector<T> constval (roi.nchannels()); ImageBuf::ConstIterator<T,T> s (src, roi); for (int c = roi.chbegin; c < roi.chend; ++c) constval[c] = s[c]; // Loop over all pixels ... for ( ; ! s.done(); ++s) { for (int c = roi.chbegin; c < roi.chend; ++c) if (constval[c] != s[c]) return false; } if (color) { ImageBuf::ConstIterator<T,float> s (src, roi); for (int c = 0; c < roi.chbegin; ++c) color[c] = 0.0f; for (int c = roi.chbegin; c < roi.chend; ++c) color[c] = s[c]; for (int c = roi.chend; c < src.nchannels(); ++c) color[c] = 0.0f; } return true; }
/// Fix all non-finite pixels (nan/inf) using the specified approach bool ImageBufAlgo::fixNonFinite (ImageBuf &src, NonFiniteFixMode mode, int *pixelsFixed, ROI roi, int nthreads) { // If no ROI is defined, use the data window of src. if (! roi.defined()) roi = get_roi(src.spec()); roi.chend = std::min (roi.chend, src.nchannels()); // Initialize if (pixelsFixed) *pixelsFixed = 0; switch (src.spec().format.basetype) { case TypeDesc::FLOAT : return fixNonFinite_<float> (src, mode, pixelsFixed, roi, nthreads); case TypeDesc::HALF : return fixNonFinite_<half> (src, mode, pixelsFixed, roi, nthreads); case TypeDesc::DOUBLE: return fixNonFinite_<double> (src, mode, pixelsFixed, roi, nthreads); default: // All other format types aren't capable of having nonfinite // pixel values. return true; } }
// DEPRECATED 2-argument version bool ImageBufAlgo::fixNonFinite (ImageBuf &dst, const ImageBuf &src, NonFiniteFixMode mode, int *pixelsFixed) { ROI roi; IBAprep (roi, &dst, &src); if (dst.nchannels() != src.nchannels()) { dst.error ("channel number mismatch: %d vs. %d", dst.spec().nchannels, src.spec().nchannels); return false; } if ((const ImageBuf *)&dst != &src) if (! dst.copy (src)) return false; return fixNonFinite (dst, mode, pixelsFixed, roi); }
bool ImageBufAlgo::convolve (ImageBuf &dst, const ImageBuf &src, const ImageBuf &kernel, bool normalize, ROI roi, int nthreads) { if (! IBAprep (roi, &dst, &src)) return false; if (dst.nchannels() != src.nchannels()) { dst.error ("channel number mismatch: %d vs. %d", dst.spec().nchannels, src.spec().nchannels); return false; } OIIO_DISPATCH_TYPES2 ("convolve", convolve_, dst.spec().format, src.spec().format, dst, src, kernel, normalize, roi, nthreads); return false; }
static inline void zero_ (ImageBuf &buf) { int chans = buf.nchannels(); for (ImageBuf::Iterator<T> pixel (buf); pixel.valid(); ++pixel) for (int i = 0; i < chans; ++i) pixel[i] = 0; }
bool ImageBufAlgo::div(ImageBuf& dst, Image_or_Const A_, Image_or_Const B_, ROI roi, int nthreads) { pvt::LoggedTimer logtime("IBA::div"); if (A_.is_img() && B_.is_img()) { const ImageBuf &A(A_.img()), &B(B_.img()); if (!IBAprep(roi, &dst, &A, &B, IBAprep_CLAMP_MUTUAL_NCHANNELS)) return false; bool ok; OIIO_DISPATCH_COMMON_TYPES3(ok, "div", div_impl, dst.spec().format, A.spec().format, B.spec().format, dst, A, B, roi, nthreads); return ok; } if (A_.is_val() && B_.is_img()) // canonicalize to A_img, B_val A_.swap(B_); if (A_.is_img() && B_.is_val()) { const ImageBuf& A(A_.img()); cspan<float> b = B_.val(); if (!IBAprep(roi, &dst, &A, IBAprep_CLAMP_MUTUAL_NCHANNELS | IBAprep_SUPPORT_DEEP)) return false; IBA_FIX_PERCHAN_LEN_DEF(b, dst.nchannels()); int nc = dst.nchannels(); float* binv = OIIO_ALLOCA(float, nc); for (int c = 0; c < nc; ++c) binv[c] = (b[c] == 0.0f) ? 0.0f : 1.0f / b[c]; b = cspan<float>(binv, nc); // re-wrap if (dst.deep()) { // While still serial, set up all the sample counts dst.deepdata()->set_all_samples(A.deepdata()->all_samples()); return mul_impl_deep(dst, A, b, roi, nthreads); } bool ok; OIIO_DISPATCH_COMMON_TYPES2(ok, "div", mul_impl, dst.spec().format, A.spec().format, dst, A, b, roi, nthreads); return ok; } // Remaining cases: error dst.error("ImageBufAlgo::div(): at least one argument must be an image"); return false; }
bool ImageBufAlgo::absdiff (ImageBuf &dst, const ImageBuf &A, const ImageBuf &B, ROI roi, int nthreads) { if (! IBAprep (roi, &dst, &A, &B)) return false; ROI origroi = roi; roi.chend = std::min (roi.chend, std::min (A.nchannels(), B.nchannels())); bool ok; OIIO_DISPATCH_COMMON_TYPES3 (ok, "absdiff", absdiff_impl, dst.spec().format, A.spec().format, B.spec().format, dst, A, B, roi, nthreads); if (roi.chend < origroi.chend && A.nchannels() != B.nchannels()) { // Edge case: A and B differed in nchannels, we allocated dst to be // the bigger of them, but adjusted roi to be the lesser. Now handle // the channels that got left out because they were not common to // all the inputs. ASSERT (roi.chend <= dst.nchannels()); roi.chbegin = roi.chend; roi.chend = origroi.chend; if (A.nchannels() > B.nchannels()) { // A exists abs (dst, A, roi, nthreads); } else { // B exists abs (dst, B, roi, nthreads); } } return ok; }
bool ImageBufAlgo::clamp (ImageBuf &dst, const float *min, const float *max, bool clampalpha01, ROI roi, int nthreads) { IBAprep (roi, &dst); std::vector<float> minvec, maxvec; if (! min) { minvec.resize (dst.nchannels(), -std::numeric_limits<float>::max()); min = &minvec[0]; } if (! max) { maxvec.resize (dst.nchannels(), std::numeric_limits<float>::max()); max = &maxvec[0]; } OIIO_DISPATCH_TYPES ("clamp", clamp_, dst.spec().format, dst, min, max, clampalpha01, roi, nthreads); return false; }
bool ImageBufAlgo::clamp (ImageBuf &dst, const ImageBuf &src, float min, float max, bool clampalpha01, ROI roi, int nthreads) { std::vector<float> minvec (src.nchannels(), min); std::vector<float> maxvec (src.nchannels(), max); return clamp (dst, src, &minvec[0], &maxvec[0], clampalpha01, roi, nthreads); }
bool ImageBufAlgo::mul (ImageBuf &R, float val, ROI roi, int nthreads) { int nc = R.nchannels(); float *vals = ALLOCA (float, nc); for (int c = 0; c < nc; ++c) vals[c] = val; return mul (R, vals, roi, nthreads); }
static inline void transfer_pixels_ (ImageBuf &buf, ColorTransfer *tfunc) { for (ImageBuf::Iterator<T> pixel (buf); pixel.valid(); ++pixel) { convert_types (buf.spec().format, pixel.rawptr(), buf.spec().format, pixel.rawptr(), buf.nchannels(), tfunc, buf.spec().alpha_channel, buf.spec().z_channel); } }
bool ImageBufAlgo::clamp (ImageBuf &dst, float min, float max, bool clampalpha01, ROI roi, int nthreads) { IBAprep (roi, &dst); std::vector<float> minvec (dst.nchannels(), min); std::vector<float> maxvec (dst.nchannels(), max); OIIO_DISPATCH_TYPES ("clamp", clamp_, dst.spec().format, dst, &minvec[0], &maxvec[0], clampalpha01, roi, nthreads); return false; }
bool ImageBufAlgo::resample (ImageBuf &dst, const ImageBuf &src, bool interpolate, ROI roi, int nthreads) { if (! IBAprep (roi, &dst, &src)) return false; if (dst.nchannels() != src.nchannels()) { dst.error ("channel number mismatch: %d vs. %d", dst.spec().nchannels, src.spec().nchannels); return false; } if (dst.spec().depth > 1 || src.spec().depth > 1) { dst.error ("ImageBufAlgo::resample does not support volume images"); return false; } OIIO_DISPATCH_TYPES2 ("resample", resample_, dst.spec().format, src.spec().format, dst, src, interpolate, roi, nthreads); return false; }
bool ImageBufAlgo::crop (ImageBuf &dst, const ImageBuf &src, ROI roi, int nthreads) { dst.clear (); roi.chend = std::min (roi.chend, src.nchannels()); IBAprep (roi, &dst, &src); OIIO_DISPATCH_TYPES2 ("crop", crop_, dst.spec().format, src.spec().format, dst, src, roi, nthreads); return false; }
bool ImageBufAlgo::histogram (const ImageBuf &A, int channel, std::vector<imagesize_t> &histogram, int bins, float min, float max, imagesize_t *submin, imagesize_t *supermax, ROI roi) { if (A.spec().format != TypeDesc::TypeFloat) { A.error ("Unsupported pixel data format '%s'", A.spec().format); return false; } if (A.nchannels() == 0) { A.error ("Input image must have at least 1 channel"); return false; } if (channel < 0 || channel >= A.nchannels()) { A.error ("Invalid channel %d for input image with channels 0 to %d", channel, A.nchannels()-1); return false; } if (bins < 1) { A.error ("The number of bins must be at least 1"); return false; } if (max <= min) { A.error ("Invalid range, min must be strictly smaller than max"); return false; } // Specified ROI -> use it. Unspecified ROI -> initialize from A. if (! roi.defined()) roi = get_roi (A.spec()); histogram_impl<float> (A, channel, histogram, bins, min, max, submin, supermax, roi); return ! A.has_error(); }
bool ImageBufAlgo::mul (ImageBuf &dst, const ImageBuf &A, float b, ROI roi, int nthreads) { if (! IBAprep (roi, &dst, &A)) return false; int nc = A.nchannels(); float *vals = ALLOCA (float, nc); for (int c = 0; c < nc; ++c) vals[c] = b; OIIO_DISPATCH_TYPES2 ("mul", mul_impl, dst.spec().format, A.spec().format, dst, A, vals, roi, nthreads); }
bool ImageBufAlgo::resize (ImageBuf &dst, const ImageBuf &src, Filter2D *filter, ROI roi, int nthreads) { if (! IBAprep (roi, &dst, &src)) return false; if (dst.nchannels() != src.nchannels()) { dst.error ("channel number mismatch: %d vs. %d", dst.spec().nchannels, src.spec().nchannels); return false; } if (dst.spec().depth > 1 || src.spec().depth > 1) { dst.error ("ImageBufAlgo::resize does not support volume images"); return false; } // Set up a shared pointer with custom deleter to make sure any // filter we allocate here is properly destroyed. boost::shared_ptr<Filter2D> filterptr ((Filter2D*)NULL, Filter2D::destroy); bool allocfilter = (filter == NULL); if (allocfilter) { // If no filter was provided, punt and just linearly interpolate. const ImageSpec &srcspec (src.spec()); const ImageSpec &dstspec (dst.spec()); float wratio = float(dstspec.full_width) / float(srcspec.full_width); float hratio = float(dstspec.full_height) / float(srcspec.full_height); float w = 2.0f * std::max (1.0f, wratio); float h = 2.0f * std::max (1.0f, hratio); filter = Filter2D::create ("triangle", w, h); filterptr.reset (filter); } OIIO_DISPATCH_TYPES2 ("resize", resize_, dst.spec().format, src.spec().format, dst, src, filter, roi, nthreads); return false; }
bool ImageBufAlgo::transpose (ImageBuf &dst, const ImageBuf &src, ROI roi, int nthreads) { if (! roi.defined()) roi = get_roi (src.spec()); roi.chend = std::min (roi.chend, src.nchannels()); ROI dst_roi (roi.ybegin, roi.yend, roi.xbegin, roi.xend, roi.zbegin, roi.zend, roi.chbegin, roi.chend); IBAprep (dst_roi, &dst); OIIO_DISPATCH_TYPES2 ("transpose", transpose_, dst.spec().format, src.spec().format, dst, src, roi, nthreads); return false; }
bool ImageBufAlgo::sub (ImageBuf &dst, const ImageBuf &A, const float *b, ROI roi, int nthreads) { if (! IBAprep (roi, &dst, &A)) return false; int nc = A.nchannels(); float *vals = ALLOCA (float, nc); for (int c = 0; c < nc; ++c) vals[c] = -b[c]; OIIO_DISPATCH_TYPES2 ("sub", add_impl, dst.spec().format, A.spec().format, dst, A, vals, roi, nthreads); return true; }
bool ImageBufAlgo::pow (ImageBuf &dst, const ImageBuf &A, float b, ROI roi, int nthreads) { if (! IBAprep (roi, &dst, &A, IBAprep_CLAMP_MUTUAL_NCHANNELS)) return false; int nc = A.nchannels(); float *vals = ALLOCA (float, nc); for (int c = 0; c < nc; ++c) vals[c] = b; bool ok; OIIO_DISPATCH_COMMON_TYPES2 (ok, "pow", pow_impl, dst.spec().format, A.spec().format, dst, A, vals, roi, nthreads); return ok; }
bool ImageBufAlgo::div (ImageBuf &dst, const ImageBuf &A, const float *b, ROI roi, int nthreads) { if (! IBAprep (roi, &dst, &A, IBAprep_CLAMP_MUTUAL_NCHANNELS)) return false; int nc = dst.nchannels(); float *binv = OIIO_ALLOCA (float, nc); for (int c = 0; c < nc; ++c) binv[c] = (b[c] == 0.0f) ? 1.0f : 1.0f/b[c]; bool ok; OIIO_DISPATCH_COMMON_TYPES2 (ok, "div", mul_impl, dst.spec().format, A.spec().format, dst, A, binv, roi, nthreads); return ok; }
static void test_arrays_like_image_multithread (ROI roi) { const float *a = (const float *)imgA.localpixels(); ASSERT(a); const float *b = (const float *)imgB.localpixels(); ASSERT(b); float *r = (float *)imgR.localpixels(); ASSERT(r); int nchannels = imgA.nchannels(); for (int y = roi.ybegin; y < roi.yend; ++y) { for (int x = roi.xbegin; x < roi.xend; ++x) { int i = (y*xres + x) * nchannels; for (int c = 0; c < nchannels; ++c) r[i+c] = a[i+c] * a[i+c] + b[i+c]; } } }
static void test_arrays_like_image (ROI roi) { const float *a = (const float *)imgA.localpixels(); ASSERT(a); const float *b = (const float *)imgB.localpixels(); ASSERT(b); float *r = (float *)imgR.localpixels(); ASSERT(r); int nchannels = imgA.nchannels(); for (int y = 0; y < yres; ++y) { for (int x = 0; x < xres; ++x) { int i = (y*xres + x) * nchannels; for (int c = 0; c < nchannels; ++c) r[i+c] = a[i+c] * a[i+c] + b[i+c]; } } }
static inline bool isMonochrome_ (const ImageBuf &src, ROI roi, int nthreads) { int nchannels = src.nchannels(); if (nchannels < 2) return true; // Loop over all pixels ... for (ImageBuf::ConstIterator<T,T> s(src, roi); ! s.done(); ++s) { T constvalue = s[roi.chbegin]; for (int c = roi.chbegin+1; c < roi.chend; ++c) if (s[c] != constvalue) return false; } return true; }