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; }
inline void compare_value (ImageBuf::ConstIterator<BUFT,float> &a, int chan, float aval, float bval, ImageBufAlgo::CompareResults &result, float &maxval, double &batcherror, double &batch_sqrerror, bool &failed, bool &warned, float failthresh, float warnthresh) { maxval = std::max (maxval, std::max (aval, bval)); double f = fabs (aval - bval); batcherror += f; batch_sqrerror += f*f; if (f > result.maxerror) { result.maxerror = f; result.maxx = a.x(); result.maxy = a.y(); result.maxz = a.z(); result.maxc = chan; } if (! warned && f > warnthresh) { ++result.nwarn; warned = true; } if (! failed && f > failthresh) { ++result.nfail; failed = true; } }
static bool circular_shift_ (ImageBuf &dst, const ImageBuf &src, int xshift, int yshift, int zshift, ROI dstroi, ROI roi, int nthreads) { if (nthreads != 1 && roi.npixels() >= 1000) { // Possible multiple thread case -- recurse via parallel_image ImageBufAlgo::parallel_image ( boost::bind(circular_shift_<DSTTYPE,SRCTYPE>, boost::ref(dst), boost::cref(src), xshift, yshift, zshift, dstroi, _1 /*roi*/, 1 /*nthreads*/), roi, nthreads); return true; } // Serial case int width = dstroi.width(), height = dstroi.height(), depth = dstroi.depth(); ImageBuf::ConstIterator<SRCTYPE,DSTTYPE> s (src, roi); ImageBuf::Iterator<DSTTYPE,DSTTYPE> d (dst); for ( ; ! s.done(); ++s) { int dx = s.x() + xshift; OIIO::wrap_periodic (dx, dstroi.xbegin, width); int dy = s.y() + yshift; OIIO::wrap_periodic (dy, dstroi.ybegin, height); int dz = s.z() + zshift; OIIO::wrap_periodic (dz, dstroi.zbegin, depth); d.pos (dx, dy, dz); if (! d.exists()) continue; for (int c = roi.chbegin; c < roi.chend; ++c) d[c] = s[c]; } return true; }
static bool transpose_ (ImageBuf &dst, const ImageBuf &src, ROI roi, int nthreads) { if (nthreads != 1 && roi.npixels() >= 1000) { // Possible multiple thread case -- recurse via parallel_image ImageBufAlgo::parallel_image ( boost::bind(transpose_<DSTTYPE,SRCTYPE>, boost::ref(dst), boost::cref(src), _1 /*roi*/, 1 /*nthreads*/), roi, nthreads); return true; } // Serial case ImageBuf::ConstIterator<SRCTYPE,DSTTYPE> s (src, roi); ImageBuf::Iterator<DSTTYPE,DSTTYPE> d (dst); for ( ; ! s.done(); ++s) { d.pos (s.y(), s.x(), s.z()); if (! d.exists()) continue; for (int c = roi.chbegin; c < roi.chend; ++c) d[c] = s[c]; } return true; }
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; }
static int action_flip (int argc, const char *argv[]) { if (ot.postpone_callback (1, action_flip, argc, argv)) return 0; ot.read (); ImageRecRef A = ot.pop(); ot.push (new ImageRec (*A, ot.allsubimages ? -1 : 0, ot.allsubimages ? -1 : 0, true, false)); int subimages = ot.curimg->subimages(); for (int s = 0; s < subimages; ++s) { int miplevels = ot.curimg->miplevels(s); for (int m = 0; m < miplevels; ++m) { const ImageBuf &Aib ((*A)(s,m)); ImageBuf &Rib ((*ot.curimg)(s,m)); ImageBuf::ConstIterator<float> a (Aib); ImageBuf::Iterator<float> r (Rib); int nchans = Rib.nchannels(); int firstscanline = Rib.ymin(); int lastscanline = Rib.ymax(); for ( ; ! r.done(); ++r) { a.pos (r.x(), lastscanline - (r.y() - firstscanline)); for (int c = 0; c < nchans; ++c) r[c] = a[c]; } } } return 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]; } }
static inline void getpixel_ (const ImageBuf &buf, int x, int y, int z, float *result, int chans) { ImageBuf::ConstIterator<T> pixel (buf, x, y, z); if (pixel.valid()) { for (int i = 0; i < chans; ++i) result[i] = pixel[i]; } else { for (int i = 0; i < chans; ++i) result[i] = 0.0f; } }
static bool flop_ (ImageBuf &dst, const ImageBuf &src, ROI roi, int nthreads) { ImageBuf::ConstIterator<S, D> s (src, roi); ImageBuf::Iterator<D, D> d (dst, roi); for ( ; ! d.done(); ++d) { s.pos (roi.xend-1 - (d.x() - roi.xbegin), d.y(), d.z()); for (int c = roi.chbegin; c < roi.chend; ++c) d[c] = s[c]; } return true; }
static bool rotate270_ (ImageBuf &dst, const ImageBuf &src, ROI dst_roi, int nthreads) { ROI dst_roi_full = dst.roi_full(); ImageBuf::ConstIterator<S, D> s (src); ImageBuf::Iterator<D, D> d (dst, dst_roi); for ( ; ! d.done(); ++d) { s.pos (dst_roi_full.yend - d.y() - 1, d.x(), d.z()); for (int c = dst_roi.chbegin; c < dst_roi.chend; ++c) d[c] = s[c]; } return true; }
static bool flip_ (ImageBuf &dst, const ImageBuf &src, ROI dst_roi, int nthreads) { ROI src_roi_full = src.roi_full(); ROI dst_roi_full = dst.roi_full(); ImageBuf::ConstIterator<S, D> s (src); ImageBuf::Iterator<D, D> d (dst, dst_roi); for ( ; ! d.done(); ++d) { int yy = d.y() - dst_roi_full.ybegin; s.pos (d.x(), src_roi_full.yend-1 - yy, d.z()); for (int c = dst_roi.chbegin; c < dst_roi.chend; ++c) d[c] = s[c]; } return true; }
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 bool transpose_ (ImageBuf &dst, const ImageBuf &src, ROI roi, int nthreads) { ImageBufAlgo::parallel_image (roi, nthreads, [&](ROI roi){ ImageBuf::ConstIterator<SRCTYPE,DSTTYPE> s (src, roi); ImageBuf::Iterator<DSTTYPE,DSTTYPE> d (dst); for ( ; ! s.done(); ++s) { d.pos (s.y(), s.x(), s.z()); if (! d.exists()) continue; for (int c = roi.chbegin; c < roi.chend; ++c) d[c] = s[c]; } }); return true; }
static inline void get_pixel_channels_ (const ImageBuf &buf, int xbegin, int xend, int ybegin, int yend, int zbegin, int zend, int chbegin, int chend, D *r, stride_t xstride, stride_t ystride, stride_t zstride) { int w = (xend-xbegin), h = (yend-ybegin); int nchans = chend - chbegin; ImageSpec::auto_stride (xstride, ystride, zstride, sizeof(D), nchans, w, h); for (ImageBuf::ConstIterator<S,D> p (buf, xbegin, xend, ybegin, yend, zbegin, zend); !p.done(); ++p) { imagesize_t offset = (p.z()-zbegin)*zstride + (p.y()-ybegin)*ystride + (p.x()-xbegin)*xstride; D *rc = (D *)((char *)r + offset); for (int c = 0; c < nchans; ++c) rc[c] = p[c+chbegin]; } }
static int action_sub (int argc, const char *argv[]) { if (ot.postpone_callback (2, action_sub, argc, argv)) return 0; ImageRecRef B (ot.pop()); ImageRecRef A (ot.pop()); ot.read (A); ot.read (B); ot.push (new ImageRec (*A, ot.allsubimages ? -1 : 0, ot.allsubimages ? -1 : 0, true, false)); int subimages = ot.curimg->subimages(); for (int s = 0; s < subimages; ++s) { int miplevels = ot.curimg->miplevels(s); for (int m = 0; m < miplevels; ++m) { const ImageBuf &Aib ((*A)(s,m)); const ImageBuf &Bib ((*B)(s,m)); if (! same_size (Aib, Bib)) { // FIXME: some day, there should be options of combining // differing images somehow. std::cerr << "oiiotool: " << argv[0] << " could not combine images of differing sizes\n"; continue; } ImageBuf &Rib ((*ot.curimg)(s,m)); ImageBuf::ConstIterator<float> a (Aib); ImageBuf::ConstIterator<float> b (Bib); ImageBuf::Iterator<float> r (Rib); int nchans = Rib.nchannels(); for ( ; ! r.done(); ++r) { a.pos (r.x(), r.y()); b.pos (r.x(), r.y()); for (int c = 0; c < nchans; ++c) r[c] = a[c] - b[c]; } } } return 0; }
static bool histogram_impl (const ImageBuf &A, int channel, std::vector<imagesize_t> &histogram, int bins, float min, float max, imagesize_t *submin, imagesize_t *supermax, ROI roi) { // Double check A's type. if (A.spec().format != BaseTypeFromC<Atype>::value) { A.error ("Unsupported pixel data format '%s'", A.spec().format); return false; } // Initialize. ImageBuf::ConstIterator<Atype, float> a (A, roi); float ratio = bins / (max-min); int bins_minus_1 = bins-1; bool submin_ok = submin != NULL; bool supermax_ok = supermax != NULL; if (submin_ok) *submin = 0; if (supermax_ok) *supermax = 0; histogram.assign(bins, 0); // Compute histogram. for ( ; ! a.done(); a++) { float c = a[channel]; if (c >= min && c < max) { // Map range min->max to 0->(bins-1). histogram[ (int) ((c-min) * ratio) ]++; } else if (c == max) { histogram[bins_minus_1]++; } else { if (submin_ok && c < min) (*submin)++; else if (supermax_ok) (*supermax)++; } } return true; }
static bool convolve_ (ImageBuf &dst, const ImageBuf &src, const ImageBuf &kernel, bool normalize, ROI roi, int nthreads) { if (nthreads != 1 && roi.npixels() >= 1000) { // Lots of pixels and request for multi threads? Parallelize. ImageBufAlgo::parallel_image ( boost::bind(convolve_<DSTTYPE,SRCTYPE>, boost::ref(dst), boost::cref(src), boost::cref(kernel), normalize, _1 /*roi*/, 1 /*nthreads*/), roi, nthreads); return true; } // Serial case float scale = 1.0f; if (normalize) { scale = 0.0f; for (ImageBuf::ConstIterator<float> k (kernel); ! k.done(); ++k) scale += k[0]; scale = 1.0f / scale; } float *sum = ALLOCA (float, roi.chend); ROI kroi = get_roi (kernel.spec()); ImageBuf::Iterator<DSTTYPE> d (dst, roi); ImageBuf::ConstIterator<SRCTYPE> s (src, roi, ImageBuf::WrapClamp); for ( ; ! d.done(); ++d) { for (int c = roi.chbegin; c < roi.chend; ++c) sum[c] = 0.0f; for (ImageBuf::ConstIterator<float> k (kernel, kroi); !k.done(); ++k) { float kval = k[0]; s.pos (d.x() + k.x(), d.y() + k.y(), d.z() + k.z()); for (int c = roi.chbegin; c < roi.chend; ++c) sum[c] += kval * s[c]; } for (int c = roi.chbegin; c < roi.chend; ++c) d[c] = scale * sum[c]; } return true; }
static bool color_count_ (const ImageBuf &src, atomic_ll *count, int ncolors, const float *color, const float *eps, ROI roi, int nthreads) { if (nthreads != 1 && roi.npixels() >= 1000) { // Lots of pixels and request for multi threads? Parallelize. ImageBufAlgo::parallel_image ( boost::bind(color_count_<T>, boost::ref(src), count, ncolors, color, eps, _1 /*roi*/, 1 /*nthreads*/), roi, nthreads); return true; } // Serial case int nchannels = src.nchannels(); long long *n = ALLOCA (long long, ncolors); for (int col = 0; col < ncolors; ++col) n[col] = 0; for (ImageBuf::ConstIterator<T> p (src, roi); !p.done(); ++p) { int coloffset = 0; for (int col = 0; col < ncolors; ++col, coloffset += nchannels) { int match = 1; for (int c = roi.chbegin; c < roi.chend; ++c) { if (fabsf(p[c] - color[coloffset+c]) > eps[c]) { match = 0; break; } } n[col] += match; } } for (int col = 0; col < ncolors; ++col) count[col] += n[col]; return true; }
inline void compare_value (ImageBuf::ConstIterator<BUFT,float> &a, int chan, float aval, float bval, ImageBufAlgo::CompareResults &result, float &maxval, double &batcherror, double &batch_sqrerror, bool &failed, bool &warned, float failthresh, float warnthresh) { if (!isfinite(aval) || !isfinite(bval)) { if (isnan(aval) == isnan(bval) && isinf(aval) == isinf(bval)) return; // NaN may match NaN, Inf may match Inf if (isfinite(result.maxerror)) { // non-finite errors trump finite ones result.maxerror = std::numeric_limits<float>::infinity(); result.maxx = a.x(); result.maxy = a.y(); result.maxz = a.z(); result.maxc = chan; return; } } maxval = std::max (maxval, std::max (aval, bval)); double f = fabs (aval - bval); batcherror += f; batch_sqrerror += f*f; // We use the awkward '!(a<=threshold)' construct so that we have // failures when f is a NaN (since all comparisons involving NaN will // return false). if (!(f <= result.maxerror)) { result.maxerror = f; result.maxx = a.x(); result.maxy = a.y(); result.maxz = a.z(); result.maxc = chan; } if (! warned && !(f <= warnthresh)) { ++result.nwarn; warned = true; } if (! failed && !(f <= failthresh)) { ++result.nfail; failed = true; } }
// DEPRECATED version bool ImageBufAlgo::add (ImageBuf &dst, const ImageBuf &A, const ImageBuf &B, int options) { // Sanity checks // dst must be distinct from A and B if ((const void *)&A == (const void *)&dst || (const void *)&B == (const void *)&dst) { dst.error ("destination image must be distinct from source"); return false; } // all three images must have the same number of channels if (A.spec().nchannels != B.spec().nchannels) { dst.error ("channel number mismatch: %d vs. %d", A.spec().nchannels, B.spec().nchannels); return false; } // If dst has not already been allocated, set it to the right size, // make it unconditinally float if (! dst.pixels_valid()) { ImageSpec dstspec = A.spec(); dstspec.set_format (TypeDesc::TypeFloat); dst.alloc (dstspec); } // Clear dst pixels if instructed to do so if (options & ADD_CLEAR_DST) { zero (dst); } ASSERT (A.spec().format == TypeDesc::FLOAT && B.spec().format == TypeDesc::FLOAT && dst.spec().format == TypeDesc::FLOAT); ImageBuf::ConstIterator<float,float> a (A); ImageBuf::ConstIterator<float,float> b (B); ImageBuf::Iterator<float> d (dst); int nchannels = A.nchannels(); // Loop over all pixels in A for ( ; a.valid(); ++a) { // Point the iterators for B and dst to the corresponding pixel if (options & ADD_RETAIN_WINDOWS) { b.pos (a.x(), a.y()); } else { // ADD_ALIGN_WINDOWS: make B line up with A b.pos (a.x()-A.xbegin()+B.xbegin(), a.y()-A.ybegin()+B.ybegin()); } d.pos (a.x(), b.y()); if (! b.valid() || ! d.valid()) continue; // Skip pixels that don't align // Add the pixel for (int c = 0; c < nchannels; ++c) d[c] = a[c] + b[c]; } return true; }
static bool colorconvert_impl (ImageBuf &R, const ImageBuf &A, const ColorProcessor* processor, bool unpremult, ROI roi, int nthreads) { if (nthreads != 1 && roi.npixels() >= 1000) { // Possible multiple thread case -- recurse via parallel_image ImageBufAlgo::parallel_image ( OIIO::bind(colorconvert_impl<Rtype,Atype>, OIIO::ref(R), OIIO::cref(A), processor, unpremult, _1 /*roi*/, 1 /*nthreads*/), roi, nthreads); return true; } // Serial case int width = roi.width(); // Temporary space to hold one RGBA scanline std::vector<float> scanline(width*4, 0.0f); // Only process up to, and including, the first 4 channels. This // does let us process images with fewer than 4 channels, which is // the intent. // FIXME: Instead of loading the first 4 channels, obey // Rspec.alpha_channel index (but first validate that the // index is set properly for normal formats) int channelsToCopy = std::min (4, roi.nchannels()); // Walk through all data in our buffer. (i.e., crop or overscan) // FIXME: What about the display window? Should this actually promote // the datawindow to be union of data + display? This is useful if // the color of black moves. (In which case non-zero sections should // now be promoted). Consider the lin->log of a roto element, where // black now moves to non-black. float * dstPtr = NULL; const float fltmin = std::numeric_limits<float>::min(); // If the processor has crosstalk, and we'll be using it, we should // reset the channels to 0 before loading each scanline. bool clearScanline = (channelsToCopy<4 && (processor->hasChannelCrosstalk() || unpremult)); ImageBuf::ConstIterator<Atype> a (A, roi); ImageBuf::Iterator<Rtype> r (R, roi); for (int k = roi.zbegin; k < roi.zend; ++k) { for (int j = roi.ybegin; j < roi.yend; ++j) { // Clear the scanline if (clearScanline) memset (&scanline[0], 0, sizeof(float)*scanline.size()); // Load the scanline dstPtr = &scanline[0]; a.rerange (roi.xbegin, roi.xend, j, j+1, k, k+1); for ( ; !a.done(); ++a, dstPtr += 4) for (int c = 0; c < channelsToCopy; ++c) dstPtr[c] = a[c]; // Optionally unpremult if ((channelsToCopy >= 4) && unpremult) { for (int i = 0; i < width; ++i) { float alpha = scanline[4*i+3]; if (alpha > fltmin) { scanline[4*i+0] /= alpha; scanline[4*i+1] /= alpha; scanline[4*i+2] /= alpha; } } } // Apply the color transformation in place processor->apply (&scanline[0], width, 1, 4, sizeof(float), 4*sizeof(float), width*4*sizeof(float)); // Optionally premult if ((channelsToCopy >= 4) && unpremult) { for (int i = 0; i < width; ++i) { float alpha = scanline[4*i+3]; if (alpha > fltmin) { scanline[4*i+0] *= alpha; scanline[4*i+1] *= alpha; scanline[4*i+2] *= alpha; } } } // Store the scanline dstPtr = &scanline[0]; r.rerange (roi.xbegin, roi.xend, j, j+1, k, k+1); for ( ; !r.done(); ++r, dstPtr += 4) for (int c = 0; c < channelsToCopy; ++c) r[c] = dstPtr[c]; } } return true; }
static bool resize_ (ImageBuf &dst, const ImageBuf &src, Filter2D *filter, ROI roi, int nthreads) { if (nthreads != 1 && roi.npixels() >= 1000) { // Lots of pixels and request for multi threads? Parallelize. ImageBufAlgo::parallel_image ( boost::bind(resize_<DSTTYPE,SRCTYPE>, boost::ref(dst), boost::cref(src), filter, _1 /*roi*/, 1 /*nthreads*/), roi, nthreads); return true; } // Serial case const ImageSpec &srcspec (src.spec()); const ImageSpec &dstspec (dst.spec()); int nchannels = dstspec.nchannels; // Local copies of the source image window, converted to float float srcfx = srcspec.full_x; float srcfy = srcspec.full_y; float srcfw = srcspec.full_width; float srcfh = srcspec.full_height; // Ratios of dst/src size. Values larger than 1 indicate that we // are maximizing (enlarging the image), and thus want to smoothly // interpolate. Values less than 1 indicate that we are minimizing // (shrinking the image), and thus want to properly filter out the // high frequencies. float xratio = float(dstspec.full_width) / srcfw; // 2 upsize, 0.5 downsize float yratio = float(dstspec.full_height) / srcfh; float dstfx = dstspec.full_x; float dstfy = dstspec.full_y; float dstfw = dstspec.full_width; float dstfh = dstspec.full_height; float dstpixelwidth = 1.0f / dstfw; float dstpixelheight = 1.0f / dstfh; float *pel = ALLOCA (float, nchannels); float filterrad = filter->width() / 2.0f; // radi,radj is the filter radius, as an integer, in source pixels. We // will filter the source over [x-radi, x+radi] X [y-radj,y+radj]. int radi = (int) ceilf (filterrad/xratio); int radj = (int) ceilf (filterrad/yratio); int xtaps = 2*radi + 1; int ytaps = 2*radj + 1; bool separable = filter->separable(); float *xfiltval = NULL, *yfiltval = NULL; if (separable) { // Allocate temp space to cache the filter weights xfiltval = ALLOCA (float, xtaps); yfiltval = ALLOCA (float, ytaps); } #if 0 std::cerr << "Resizing " << srcspec.full_width << "x" << srcspec.full_height << " to " << dstspec.full_width << "x" << dstspec.full_height << "\n"; std::cerr << "ratios = " << xratio << ", " << yratio << "\n"; std::cerr << "examining src filter support radius of " << radi << " x " << radj << " pixels\n"; std::cerr << "dst range " << roi << "\n"; std::cerr << "separable filter\n"; #endif // We're going to loop over all output pixels we're interested in. // // (s,t) = NDC space coordinates of the output sample we are computing. // This is the "sample point". // (src_xf, src_xf) = source pixel space float coordinates of the // sample we're computing. We want to compute the weighted sum // of all the source image pixels that fall under the filter when // centered at that location. // (src_x, src_y) = image space integer coordinates of the floor, // i.e., the closest pixel in the source image. // src_xf_frac and src_yf_frac are the position within that pixel // of our sample. ImageBuf::Iterator<DSTTYPE> out (dst, roi); for (int y = roi.ybegin; y < roi.yend; ++y) { float t = (y-dstfy+0.5f)*dstpixelheight; float src_yf = srcfy + t * srcfh; int src_y; float src_yf_frac = floorfrac (src_yf, &src_y); // If using separable filters, our vertical set of filter tap // weights will be the same for the whole scanline we're on. Just // compute and normalize them once. float totalweight_y = 0.0f; if (separable) { for (int j = 0; j < ytaps; ++j) { float w = filter->yfilt (yratio * (j-radj-(src_yf_frac-0.5f))); yfiltval[j] = w; totalweight_y += w; } for (int i = 0; i <= ytaps; ++i) yfiltval[i] /= totalweight_y; } for (int x = roi.xbegin; x < roi.xend; ++x) { float s = (x-dstfx+0.5f)*dstpixelwidth; float src_xf = srcfx + s * srcfw; int src_x; float src_xf_frac = floorfrac (src_xf, &src_x); for (int c = 0; c < nchannels; ++c) pel[c] = 0.0f; if (separable) { // Cache and normalize the horizontal filter tap weights // just once for this (x,y) position, reuse for all vertical // taps. float totalweight_x = 0.0f; for (int i = 0; i < xtaps; ++i) { float w = filter->xfilt (xratio * (i-radi-(src_xf_frac-0.5f))); xfiltval[i] = w; totalweight_x += w; } if (totalweight_x != 0.0f) { for (int i = 0; i < xtaps; ++i) // normalize x filter xfiltval[i] /= totalweight_x; // weights ImageBuf::ConstIterator<SRCTYPE> srcpel (src, src_x-radi, src_x+radi+1, src_y-radj, src_y+radj+1, 0, 1, ImageBuf::WrapClamp); for (int j = -radj; j <= radj; ++j) { float wy = yfiltval[j+radj]; if (wy == 0.0f) { // 0 weight for this y tap -- move to next line srcpel.pos (srcpel.x(), srcpel.y()+1, srcpel.z()); continue; } for (int i = 0; i < xtaps; ++i, ++srcpel) { float w = wy * xfiltval[i]; for (int c = 0; c < nchannels; ++c) pel[c] += w * srcpel[c]; } } } // Copy the pixel value (already normalized) to the output. DASSERT (out.x() == x && out.y() == y); if (totalweight_y == 0.0f) { // zero it out for (int c = 0; c < nchannels; ++c) out[c] = 0.0f; } else { for (int c = 0; c < nchannels; ++c) out[c] = pel[c]; } } else { // Non-separable float totalweight = 0.0f; ImageBuf::ConstIterator<SRCTYPE> srcpel (src, src_x-radi, src_x+radi+1, src_y-radi, src_y+radi+1, 0, 1, ImageBuf::WrapClamp); for (int j = -radj; j <= radj; ++j) { for (int i = -radi; i <= radi; ++i, ++srcpel) { float w = (*filter)(xratio * (i-(src_xf_frac-0.5f)), yratio * (j-(src_yf_frac-0.5f))); totalweight += w; if (w == 0.0f) continue; DASSERT (! srcpel.done()); for (int c = 0; c < nchannels; ++c) pel[c] += w * srcpel[c]; } } DASSERT (srcpel.done()); // Rescale pel to normalize the filter and write it to the // output image. DASSERT (out.x() == x && out.y() == y); if (totalweight == 0.0f) { // zero it out for (int c = 0; c < nchannels; ++c) out[c] = 0.0f; } else { for (int c = 0; c < nchannels; ++c) out[c] = pel[c] / totalweight; } } ++out; } } return true; }
void IvImage::pixel_transform(bool srgb_to_linear, int color_mode, int select_channel) { /// This table obeys the following function: /// /// unsigned char srgb2linear(unsigned char x) /// { /// float x_f = x/255.0; /// float x_l = 0.0; /// if (x_f <= 0.04045) /// x_l = x_f/12.92; /// else /// x_l = powf((x_f+0.055)/1.055,2.4); /// return (unsigned char)(x_l * 255 + 0.5) /// } /// /// It's used to transform from sRGB color space to linear color space. static const unsigned char srgb_to_linear_lut[256] = { 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 8, 8, 8, 8, 9, 9, 9, 10, 10, 10, 11, 11, 12, 12, 12, 13, 13, 13, 14, 14, 15, 15, 16, 16, 17, 17, 17, 18, 18, 19, 19, 20, 20, 21, 22, 22, 23, 23, 24, 24, 25, 25, 26, 27, 27, 28, 29, 29, 30, 30, 31, 32, 32, 33, 34, 35, 35, 36, 37, 37, 38, 39, 40, 41, 41, 42, 43, 44, 45, 45, 46, 47, 48, 49, 50, 51, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 76, 77, 78, 79, 80, 81, 82, 84, 85, 86, 87, 88, 90, 91, 92, 93, 95, 96, 97, 99, 100, 101, 103, 104, 105, 107, 108, 109, 111, 112, 114, 115, 116, 118, 119, 121, 122, 124, 125, 127, 128, 130, 131, 133, 134, 136, 138, 139, 141, 142, 144, 146, 147, 149, 151, 152, 154, 156, 157, 159, 161, 163, 164, 166, 168, 170, 171, 173, 175, 177, 179, 181, 183, 184, 186, 188, 190, 192, 194, 196, 198, 200, 202, 204, 206, 208, 210, 212, 214, 216, 218, 220, 222, 224, 226, 229, 231, 233, 235, 237, 239, 242, 244, 246, 248, 250, 253, 255 }; unsigned char correction_table[256]; int total_channels = spec().nchannels; int color_channels = spec().nchannels; int max_channels = m_corrected_image.nchannels(); // FIXME: Now with the iterator and data proxy in place, it should be // trivial to apply the transformations to any kind of data, not just // UINT8. if (spec().format != TypeDesc::UINT8 || ! m_corrected_image.localpixels()) { return; } if (color_channels > 3) { color_channels = 3; } else if (color_channels == 2) { color_channels = 1; } // This image is Luminance or Luminance + Alpha, and we are asked to show // luminance. if (color_channels == 1 && color_mode == 3) { color_mode = 0; // Just copy as usual. } // Happy path: if (! srgb_to_linear && color_mode <= 1 && m_gamma == 1.0 && m_exposure == 0.0) { ImageBuf::ConstIterator<unsigned char, unsigned char> src (*this); ImageBuf::Iterator<unsigned char, unsigned char> dst (m_corrected_image); for ( ; src.valid (); ++src) { dst.pos (src.x(), src.y()); for (int i = 0; i < max_channels; i++) dst[i] = src[i]; } return; } // fill the correction_table if (gamma() == 1.0 && exposure() == 0.0) { for (int pixelvalue = 0; pixelvalue < 256; ++pixelvalue) { correction_table[pixelvalue] = pixelvalue; } } else { float inv_gamma = 1.0/gamma(); float gain = powf (2.0f, exposure()); for (int pixelvalue = 0; pixelvalue < 256; ++pixelvalue) { float pv_f = converter (pixelvalue); pv_f = clamp (calc_exposure (pv_f, gain, inv_gamma), 0.0f, 1.0f); correction_table[pixelvalue] = (unsigned char) (pv_f*255 + 0.5); } } ImageBuf::ConstIterator<unsigned char, unsigned char> src (*this); ImageBuf::Iterator<unsigned char, unsigned char> dst (m_corrected_image); for ( ; src.valid(); ++src) { dst.pos (src.x(), src.y()); if (color_mode == 0 || color_mode == 1) { // RGBA, RGB modes. int ch = 0; for (ch = 0; ch < color_channels; ch++) { if (srgb_to_linear) dst[ch] = correction_table[srgb_to_linear_lut[src[ch]]]; else dst[ch] = correction_table[src[ch]]; } for (; ch < max_channels; ch++) { dst[ch] = src[ch]; } } else if (color_mode == 3) { // Convert RGB to luminance, (Rec. 709 luma coefficients). float luminance; if (srgb_to_linear) { luminance = converter (srgb_to_linear_lut[src[0]])*0.2126f + converter (srgb_to_linear_lut[src[1]])*0.7152f + converter (srgb_to_linear_lut[src[2]])*0.0722f; } else { luminance = converter (src[0])*0.2126f + converter (src[1])*0.7152f + converter (src[2])*0.0722f; } unsigned char val = (unsigned char) (clamp (luminance, 0.0f, 1.0f) * 255.0 + 0.5); val = correction_table[val]; dst[0] = val; dst[1] = val; dst[2] = val; // Handle the rest of the channels for (int ch = 3; ch < total_channels; ++ch) { dst[ch] = src[ch]; } } else { // Single channel, heatmap. unsigned char v = 0; if (select_channel < color_channels) { if (srgb_to_linear) v = correction_table[srgb_to_linear_lut[src[select_channel]]]; else v = correction_table[src[select_channel]]; } else if (select_channel < total_channels) { v = src[select_channel]; } int ch = 0; for (; ch < color_channels; ++ch) { dst[ch] = v; } for (; ch < max_channels; ++ch) { dst[ch] = src[ch]; } } } }
static bool resample_ (ImageBuf &dst, const ImageBuf &src, bool interpolate, ROI roi, int nthreads) { if (nthreads != 1 && roi.npixels() >= 1000) { // Lots of pixels and request for multi threads? Parallelize. ImageBufAlgo::parallel_image ( boost::bind(resample_<DSTTYPE,SRCTYPE>, boost::ref(dst), boost::cref(src), interpolate, _1 /*roi*/, 1 /*nthreads*/), roi, nthreads); return true; } // Serial case const ImageSpec &srcspec (src.spec()); const ImageSpec &dstspec (dst.spec()); int nchannels = src.nchannels(); // Local copies of the source image window, converted to float float srcfx = srcspec.full_x; float srcfy = srcspec.full_y; float srcfw = srcspec.full_width; float srcfh = srcspec.full_height; float dstfx = dstspec.full_x; float dstfy = dstspec.full_y; float dstfw = dstspec.full_width; float dstfh = dstspec.full_height; float dstpixelwidth = 1.0f / dstfw; float dstpixelheight = 1.0f / dstfh; float *pel = ALLOCA (float, nchannels); ImageBuf::Iterator<DSTTYPE> out (dst, roi); ImageBuf::ConstIterator<SRCTYPE> srcpel (src); for (int y = roi.ybegin; y < roi.yend; ++y) { // s,t are NDC space float t = (y-dstfy+0.5f)*dstpixelheight; // src_xf, src_xf are image space float coordinates float src_yf = srcfy + t * srcfh - 0.5f; // src_x, src_y are image space integer coordinates of the floor int src_y; (void) floorfrac (src_yf, &src_y); for (int x = roi.xbegin; x < roi.xend; ++x) { float s = (x-dstfx+0.5f)*dstpixelwidth; float src_xf = srcfx + s * srcfw - 0.5f; int src_x; (void) floorfrac (src_xf, &src_x); if (interpolate) { src.interppixel (src_xf, src_yf, pel); for (int c = roi.chbegin; c < roi.chend; ++c) out[c] = pel[c]; } else { srcpel.pos (src_x, src_y, 0); for (int c = roi.chbegin; c < roi.chend; ++c) out[c] = srcpel[c]; } ++out; } } return true; }