Exemplo n.º 1
0
static bool
clamp_ (ImageBuf &dst, const ImageBuf &src,
        const float *min, const float *max,
        bool clampalpha01, ROI roi, int nthreads)
{
    ImageBufAlgo::parallel_image (roi, nthreads, [&](ROI roi){
        ImageBuf::ConstIterator<S> s (src, roi);
        for (ImageBuf::Iterator<D> d (dst, roi);  ! d.done();  ++d, ++s) {
            for (int c = roi.chbegin;  c < roi.chend;  ++c)
                d[c] = OIIO::clamp<float> (s[c], min[c], max[c]);
        }
        int a = src.spec().alpha_channel;
        if (clampalpha01 && a >= roi.chbegin && a < roi.chend) {
            for (ImageBuf::Iterator<D> d (dst, roi);  ! d.done();  ++d)
                d[a] = OIIO::clamp<float> (d[a], 0.0f, 1.0f);
        }
    });
    return true;
}
Exemplo n.º 2
0
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;
}
Exemplo n.º 3
0
static bool
pow_impl (ImageBuf &R, const ImageBuf &A, const float *b,
          ROI roi, int nthreads)
{
    ImageBufAlgo::parallel_image (roi, nthreads, [&](ROI roi){
        ImageBuf::ConstIterator<Atype> a (A, roi);
        for (ImageBuf::Iterator<Rtype> r (R, roi);  !r.done();  ++r, ++a)
            for (int c = roi.chbegin;  c < roi.chend;  ++c)
                r[c] = pow (a[c], b[c]);
    });
    return true;
}
Exemplo n.º 4
0
static bool
absdiff_impl (ImageBuf &R, const ImageBuf &A, const ImageBuf &B,
              ROI roi, int nthreads)
{
    ImageBufAlgo::parallel_image (roi, nthreads, [&](ROI roi){
        ImageBuf::Iterator<Rtype> r (R, roi);
        ImageBuf::ConstIterator<Atype> a (A, roi);
        ImageBuf::ConstIterator<Btype> b (B, roi);
        for ( ;  !r.done();  ++r, ++a, ++b)
            for (int c = roi.chbegin;  c < roi.chend;  ++c)
                r[c] = std::abs (a[c] - b[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
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;
}
Exemplo n.º 7
0
static bool
channel_sum_ (ImageBuf &dst, const ImageBuf &src,
              const float *weights, ROI roi, int nthreads)
{
    ImageBufAlgo::parallel_image (roi, nthreads, [&](ROI roi){
        ImageBuf::Iterator<D> d (dst, roi);
        ImageBuf::ConstIterator<S> s (src, roi);
        for ( ;  !d.done();  ++d, ++s) {
            float sum = 0.0f;
            for (int c = roi.chbegin;  c < roi.chend;  ++c)
                sum += s[c] * weights[c];
            d[0] = sum;
        }
    });
    return true;
}
Exemplo n.º 8
0
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;
}
Exemplo n.º 9
0
static bool
mul_impl (ImageBuf &R, const float *val, ROI roi, int nthreads)
{
    if (nthreads != 1 && roi.npixels() >= 1000) {
        // Possible multiple thread case -- recurse via parallel_image
        ImageBufAlgo::parallel_image (
            boost::bind(mul_impl<Rtype>, boost::ref(R), val,
                        _1 /*roi*/, 1 /*nthreads*/),
            roi, nthreads);
        return true;
    }

    ImageBuf::Iterator<Rtype> r (R, roi);
    for (ImageBuf::Iterator<Rtype> r (R, roi);  !r.done();  ++r)
        for (int c = roi.chbegin;  c < roi.chend;  ++c)
            r[c] = r[c] * val[c];
    return true;
}
Exemplo n.º 10
0
static bool
fill_const_ (ImageBuf &dst, const float *values, ROI roi=ROI(), int nthreads=1)
{
    if (nthreads != 1 && roi.npixels() >= 1000) {
        // Lots of pixels and request for multi threads? Parallelize.
        ImageBufAlgo::parallel_image (
            OIIO::bind(fill_const_<T>, OIIO::ref(dst), values,
                        _1 /*roi*/, 1 /*nthreads*/),
            roi, nthreads);
        return true;
    }

    // Serial case
    for (ImageBuf::Iterator<T> p (dst, roi);  !p.done();  ++p)
        for (int c = roi.chbegin;  c < roi.chend;  ++c)
            p[c] = values[c];
    return true;
}
Exemplo n.º 11
0
static bool
rangeexpand_ (ImageBuf &R, bool useluma, ROI roi, int nthreads)
{
    if (nthreads != 1 && roi.npixels() >= 1000) {
        // Possible multiple thread case -- recurse via parallel_image
        ImageBufAlgo::parallel_image (
            boost::bind(rangeexpand_<Rtype>, boost::ref(R), useluma,
                        _1 /*roi*/, 1 /*nthreads*/),
            roi, nthreads);
        return true;
    }

    const ImageSpec &Rspec (R.spec());
    int alpha_channel = Rspec.alpha_channel;
    int z_channel = Rspec.z_channel;
    if (roi.nchannels() < 3 ||
        (alpha_channel >= roi.chbegin && alpha_channel < roi.chbegin+3) ||
        (z_channel >= roi.chbegin && z_channel < roi.chbegin+3)) {
        useluma = false;  // No way to use luma
    }

    ImageBuf::Iterator<Rtype> r (R, roi);
    for (ImageBuf::Iterator<Rtype> r (R, roi);  !r.done();  ++r) {
        if (useluma) {
            float luma = 0.21264f * r[roi.chbegin] + 0.71517f * r[roi.chbegin+1] + 0.07219f * r[roi.chbegin+2];
            if (fabsf(luma) <= 1.0f)
                continue;  // Not HDR, no range compression needed
            float scale = rangeexpand (luma) / luma;
            for (int c = roi.chbegin; c < roi.chend; ++c) {
                if (c == alpha_channel || c == z_channel)
                    continue;
                r[c] = r[c] * scale;
            }
        } else {
            for (int c = roi.chbegin; c < roi.chend; ++c) {
                if (c == alpha_channel || c == z_channel)
                    continue;
                r[c] = rangeexpand (r[c]);
            }
        }
    }
    return true;
}
Exemplo n.º 12
0
static bool
pow_impl (ImageBuf &R, const ImageBuf &A, const float *b,
          ROI roi, int nthreads)
{
    if (nthreads != 1 && roi.npixels() >= 1000) {
        // Possible multiple thread case -- recurse via parallel_image
        ImageBufAlgo::parallel_image (
            boost::bind(pow_impl<Rtype,Atype>, boost::ref(R), boost::cref(A), b,
                        _1 /*roi*/, 1 /*nthreads*/),
            roi, nthreads);
        return true;
    }

    ImageBuf::ConstIterator<Atype> a (A, roi);
    for (ImageBuf::Iterator<Rtype> r (R, roi);  !r.done();  ++r, ++a)
        for (int c = roi.chbegin;  c < roi.chend;  ++c)
            r[c] = pow (a[c], b[c]);
    return true;
}
Exemplo n.º 13
0
void test_paste ()
{
    std::cout << "test paste\n";
    // Create the source image, make it a gradient
    ImageSpec Aspec (4, 4, 3, TypeDesc::FLOAT);
    ImageBuf A (Aspec);
    for (ImageBuf::Iterator<float> it (A);  !it.done();  ++it) {
        it[0] = float(it.x()) / float(Aspec.width-1);
        it[1] = float(it.y()) / float(Aspec.height-1);
        it[2] = 0.1f;
    }

    // Create destination image -- black it out
    ImageSpec Bspec (8, 8, 3, TypeDesc::FLOAT);
    ImageBuf B (Bspec);
    float gray[3] = { .1, .1, .1 };
    ImageBufAlgo::fill (B, gray);

    // Paste a few pixels from A into B -- include offsets
    ImageBufAlgo::paste (B, 2, 2, 0, 1 /* chan offset */,
                         A, ROI(1, 4, 1, 4));

    // Spot check
    float a[3], b[3];
    B.getpixel (1, 1, 0, b);
    OIIO_CHECK_EQUAL (b[0], gray[0]);
    OIIO_CHECK_EQUAL (b[1], gray[1]);
    OIIO_CHECK_EQUAL (b[2], gray[2]);

    B.getpixel (2, 2, 0, b);
    A.getpixel (1, 1, 0, a);
    OIIO_CHECK_EQUAL (b[0], gray[0]);
    OIIO_CHECK_EQUAL (b[1], a[0]);
    OIIO_CHECK_EQUAL (b[2], a[1]);

    B.getpixel (3, 4, 0, b);
    A.getpixel (2, 3, 0, a);
    OIIO_CHECK_EQUAL (b[0], gray[0]);
    OIIO_CHECK_EQUAL (b[1], a[0]);
    OIIO_CHECK_EQUAL (b[2], a[1]);
}
Exemplo n.º 14
0
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;
}
Exemplo n.º 15
0
static bool
crop_ (ImageBuf &dst, const ImageBuf &src,
       ROI roi, int nthreads=1)
{
    if (nthreads != 1 && roi.npixels() >= 1000) {
        // Lots of pixels and request for multi threads? Parallelize.
        ImageBufAlgo::parallel_image (
            boost::bind(crop_<D,S>, boost::ref(dst), boost::cref(src),
                        _1 /*roi*/, 1 /*nthreads*/),
            roi, nthreads);
        return true;
    }

    // Serial case
    ImageBuf::ConstIterator<S,D> s (src, roi);
    ImageBuf::Iterator<D,D> d (dst, roi);
    for ( ;  ! d.done();  ++d, ++s) {
        for (int c = roi.chbegin;  c < roi.chend;  ++c)
            d[c] = s[c];
    }
    return true;
}
Exemplo n.º 16
0
bool
ImageBufAlgo::histogram_draw (ImageBuf &R,
                              const std::vector<imagesize_t> &histogram)
{
    // Fail if there are no bins to draw.
    int bins = histogram.size();
    if (bins == 0) {
        R.error ("There are no bins to draw, the histogram is empty");
        return false;
    }

    // Check R and modify it if needed.
    int height = R.spec().height;
    if (R.spec().format != TypeDesc::TypeFloat || R.nchannels() != 1 ||
        R.spec().width != bins) {
        ImageSpec newspec = ImageSpec (bins, height, 1, TypeDesc::FLOAT);
        R.reset ("dummy", newspec);
    }

    // Fill output image R with white color.
    ImageBuf::Iterator<float, float> r (R);
    for ( ; ! r.done(); ++r)
        r[0] = 1;

    // Draw histogram left->right, bottom->up.
    imagesize_t max = *std::max_element (histogram.begin(), histogram.end());
    for (int b = 0; b < bins; b++) {
        int bin_height = (int) ((float)histogram[b]/(float)max*height + 0.5f);
        if (bin_height != 0) {
            // Draw one bin at column b.
            for (int j = 1; j <= bin_height; j++) {
                int row = height - j;
                r.pos (b, row);
                r[0] = 0;
            }
        }
    }
    return true;
}
Exemplo n.º 17
0
static bool
mad_implf (ImageBuf &R, const ImageBuf &A, const float *b, const float *c,
          ROI roi, int nthreads)
{
    if (nthreads != 1 && roi.npixels() >= 1000) {
        // Possible multiple thread case -- recurse via parallel_image
        ImageBufAlgo::parallel_image (
            boost::bind(mad_implf<Rtype,Atype>, boost::ref(R),
                        boost::cref(A), b, c,
                        _1 /*roi*/, 1 /*nthreads*/),
            roi, nthreads);
        return true;
    }

    // Serial case
    ImageBuf::Iterator<Rtype> r (R, roi);
    ImageBuf::ConstIterator<Atype> a (A, roi);
    for ( ;  !r.done();  ++r, ++a)
        for (int ch = roi.chbegin;  ch < roi.chend;  ++ch)
            r[ch] = a[ch] * b[ch] + c[ch];
    return true;
}
Exemplo n.º 18
0
static bool
fill_tb_ (ImageBuf &dst, const float *top, const float *bottom,
       ROI origroi, ROI roi=ROI(), int nthreads=1)
{
    if (nthreads != 1 && roi.npixels() >= 1000) {
        // Lots of pixels and request for multi threads? Parallelize.
        ImageBufAlgo::parallel_image (
            OIIO::bind(fill_tb_<T>, OIIO::ref(dst), top, bottom,
                        origroi, _1 /*roi*/, 1 /*nthreads*/),
            roi, nthreads);
        return true;
    }

    // Serial case
    float h = std::max (1, origroi.height() - 1);
    for (ImageBuf::Iterator<T> p (dst, roi);  !p.done();  ++p) {
        float v = (p.y() - origroi.ybegin) / h;
        for (int c = roi.chbegin;  c < roi.chend;  ++c)
            p[c] = lerp (top[c], bottom[c], v);
    }
    return true;
}
Exemplo n.º 19
0
static bool
channel_sum_ (ImageBuf &dst, const ImageBuf &src,
              const float *weights, ROI roi, int nthreads)
{
    if (nthreads != 1 && roi.npixels() >= 1000) {
        // Possible multiple thread case -- recurse via parallel_image
        ImageBufAlgo::parallel_image (
            boost::bind(channel_sum_<D,S>, boost::ref(dst), boost::cref(src),
                        weights, _1 /*roi*/, 1 /*nthreads*/),
            roi, nthreads);
        return true;
    }

    ImageBuf::Iterator<D> d (dst, roi);
    ImageBuf::ConstIterator<S> s (src, roi);
    for ( ;  !d.done();  ++d, ++s) {
        float sum = 0.0f;
        for (int c = roi.chbegin;  c < roi.chend;  ++c)
            sum += s[c] * weights[c];
        d[0] = sum;
    }
    return true;
}
Exemplo n.º 20
0
static bool
sub_impl (ImageBuf &R, const ImageBuf &A, const ImageBuf &B,
          ROI roi, int nthreads)
{
    if (nthreads != 1 && roi.npixels() >= 1000) {
        // Possible multiple thread case -- recurse via parallel_image
        ImageBufAlgo::parallel_image (
            boost::bind(sub_impl<Rtype,Atype,Btype>,
                        boost::ref(R), boost::cref(A), boost::cref(B),
                        _1 /*roi*/, 1 /*nthreads*/),
            roi, nthreads);
        return true;
    }

    // Serial case
    ImageBuf::Iterator<Rtype> r (R, roi);
    ImageBuf::ConstIterator<Atype> a (A, roi);
    ImageBuf::ConstIterator<Btype> b (B, roi);
    for ( ;  !r.done();  ++r, ++a, ++b)
        for (int c = roi.chbegin;  c < roi.chend;  ++c)
            r[c] = a[c] - b[c];
    return true;
}
Exemplo n.º 21
0
static bool
premult_ (ImageBuf &R, ROI roi, int nthreads)
{
    if (nthreads != 1 && roi.npixels() >= 1000) {
        // Possible multiple thread case -- recurse via parallel_image
        ImageBufAlgo::parallel_image (
            boost::bind(premult_<Rtype>, boost::ref(R),
                        _1 /*roi*/, 1 /*nthreads*/),
            roi, nthreads);
        return true;
    }

    int alpha_channel = R.spec().alpha_channel;
    int z_channel = R.spec().z_channel;
    for (ImageBuf::Iterator<Rtype> r (R, roi);  !r.done();  ++r) {
        float alpha = r[alpha_channel];
        if (alpha == 1.0f)
            continue;
        for (int c = roi.chbegin;  c < roi.chend;  ++c)
            if (c != alpha_channel && c != z_channel)
                r[c] = r[c] * alpha;
    }
    return true;
}
Exemplo n.º 22
0
static bool
mad_impl (ImageBuf &R, const ImageBuf &A, const ImageBuf &B, const ImageBuf &C,
          ROI roi, int nthreads)
{
    if (nthreads != 1 && roi.npixels() >= 1000) {
        // Possible multiple thread case -- recurse via parallel_image
        ImageBufAlgo::parallel_image (
            OIIO::bind(mad_impl<Rtype,ABCtype>, OIIO::ref(R),
                        OIIO::cref(A), OIIO::cref(B), OIIO::cref(C),
                        _1 /*roi*/, 1 /*nthreads*/),
            roi, nthreads);
        return true;
    }

    // Serial case
    ImageBuf::Iterator<Rtype> r (R, roi);
    ImageBuf::ConstIterator<ABCtype> a (A, roi);
    ImageBuf::ConstIterator<ABCtype> b (B, roi);
    ImageBuf::ConstIterator<ABCtype> c (C, roi);
    for ( ;  !r.done();  ++r, ++a, ++b, ++c)
        for (int ch = roi.chbegin;  ch < roi.chend;  ++ch)
            r[ch] = a[ch] * b[ch] + c[ch];
    return true;
}
Exemplo n.º 23
0
static bool
flatten_ (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(flatten_<DSTTYPE>, boost::ref(dst), boost::cref(src),
                        _1 /*roi*/, 1 /*nthreads*/),
            roi, nthreads);
        return true;
    }

    const ImageSpec &srcspec (src.spec());
    int nc = srcspec.nchannels;

    int alpha_channel, RA_channel, GA_channel, BA_channel;
    int R_channel, G_channel, B_channel;
    int Z_channel, Zback_channel;
    if (! find_deep_channels (srcspec, alpha_channel,
                              RA_channel, GA_channel, BA_channel,
                              R_channel, G_channel, B_channel,
                              Z_channel, Zback_channel)) {
        dst.error ("No alpha channel could be identified");
        return false;
    }
    ASSERT (alpha_channel >= 0 ||
            (RA_channel >= 0 && GA_channel >= 0 && BA_channel >= 0));
    float *val = ALLOCA (float, nc);
    float &RAval (RA_channel >= 0 ? val[RA_channel] : val[alpha_channel]);
    float &GAval (GA_channel >= 0 ? val[GA_channel] : val[alpha_channel]);
    float &BAval (BA_channel >= 0 ? val[BA_channel] : val[alpha_channel]);

    for (ImageBuf::Iterator<DSTTYPE> r (dst, roi);  !r.done();  ++r) {
        int x = r.x(), y = r.y(), z = r.z();
        int samps = src.deep_samples (x, y, z);
        // Clear accumulated values for this pixel (0 for colors, big for Z)
        memset (val, 0, nc*sizeof(float));
        if (Z_channel >= 0 && samps == 0)
            val[Z_channel] = 1.0e30;
        if (Zback_channel >= 0 && samps == 0)
            val[Zback_channel] = 1.0e30;
        for (int s = 0;  s < samps;  ++s) {
            float RA = RAval, GA = GAval, BA = BAval;  // make copies
            float alpha = (RA + GA + BA) / 3.0f;
            if (alpha >= 1.0f)
                break;
            for (int c = 0;  c < nc;  ++c) {
                float v = src.deep_value (x, y, z, c, s);
                if (c == Z_channel || c == Zback_channel)
                    val[c] *= alpha;  // because Z are not premultiplied
                float a;
                if (c == R_channel)
                    a = RA;
                else if (c == G_channel)
                    a = GA;
                else if (c == B_channel)
                    a = BA;
                else
                    a = alpha;
                val[c] += (1.0f - a) * v;
            }
        }

        for (int c = roi.chbegin;  c < roi.chend;  ++c)
            r[c] = val[c];
    }

    return true;
}
Exemplo n.º 24
0
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;
}