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 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;
}
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;
}
// 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;
}
Exemple #6
0
int
main (int argc, char *argv[])
{
    getargs (argc, argv);

    std::cout << "Comparing \"" << filenames[0] 
             << "\" and \"" << filenames[1] << "\"\n";

    // Create a private ImageCache so we can customize its cache size
    // and instruct it store everything internally as floats.
    ImageCache *imagecache = ImageCache::create (true);
    imagecache->attribute ("forcefloat", 1);
    if (sizeof(void *) == 4)  // 32 bit or 64?
        imagecache->attribute ("max_memory_MB", 512.0);
    else
        imagecache->attribute ("max_memory_MB", 2048.0);
    imagecache->attribute ("autotile", 256);
#ifdef DEBUG
    imagecache->attribute ("statistics:level", 2);
#endif

    ImageBuf img0, img1;
    if (! read_input (filenames[0], img0, imagecache) ||
        ! read_input (filenames[1], img1, imagecache))
        return ErrFile;
//    ImageSpec spec0 = img0.spec();  // stash it

    int ret = ErrOK;
    for (int subimage = 0;  subimage < img0.nsubimages();  ++subimage) {
        if (subimage > 0 && !compareall)
            break;
        if (subimage >= img1.nsubimages())
            break;

        if (compareall) {
            std::cout << "Subimage " << subimage << ": ";
            std::cout << img0.spec().width << " x " << img0.spec().height;
            if (img0.spec().depth > 1)
                std::cout << " x " << img0.spec().depth;
            std::cout << ", " << img0.spec().nchannels << " channel\n";
        }

        if (! read_input (filenames[0], img0, imagecache, subimage) ||
            ! read_input (filenames[1], img1, imagecache, subimage))
            return ErrFile;

        if (img0.nmiplevels() != img1.nmiplevels()) {
            std::cout << "Files do not match in their number of MIPmap levels\n";
        }

        for (int m = 0;  m < img0.nmiplevels();  ++m) {
            if (m > 0 && !compareall)
                break;
            if (m > 0 && img0.nmiplevels() != img1.nmiplevels()) {
                std::cout << "Files do not match in their number of MIPmap levels\n";
                ret = ErrDifferentSize;
                break;
            }

            if (! read_input (filenames[0], img0, imagecache, subimage, m) ||
                ! read_input (filenames[1], img1, imagecache, subimage, m))
                return ErrFile;

            if (compareall && img0.nmiplevels() > 1) {
                std::cout << " MIP level " << m << ": ";
                std::cout << img0.spec().width << " x " << img0.spec().height;
                if (img0.spec().depth > 1)
                    std::cout << " x " << img0.spec().depth;
                std::cout << ", " << img0.spec().nchannels << " channel\n";
            }

            // Compare the dimensions of the images.  Fail if they
            // aren't the same resolution and number of channels.  No
            // problem, though, if they aren't the same data type.
            if (! same_size (img0, img1)) {
                std::cout << "Images do not match in size: ";
                std::cout << "(" << img0.spec().width << "x" << img0.spec().height;
                if (img0.spec().depth > 1)
                    std::cout << "x" << img0.spec().depth;
                std::cout << "x" << img0.spec().nchannels << ")";
                std::cout << " versus ";
                std::cout << "(" << img1.spec().width << "x" << img1.spec().height;
                if (img1.spec().depth > 1)
                    std::cout << "x" << img1.spec().depth;
                std::cout << "x" << img1.spec().nchannels << ")\n";
                ret = ErrDifferentSize;
                break;
            }

            int npels = img0.spec().width * img0.spec().height * img0.spec().depth;
            ASSERT (img0.spec().format == TypeDesc::FLOAT);

            // Compare the two images.
            //
            ImageBufAlgo::CompareResults cr;
            ImageBufAlgo::compare (img0, img1, failthresh, warnthresh, cr);

            int yee_failures = 0;
            if (perceptual)
                yee_failures = ImageBufAlgo::compare_Yee (img0, img1);

            // Print the report
            //
            std::cout << "  Mean error = ";
            safe_double_print (cr.meanerror);
            std::cout << "  RMS error = ";
            safe_double_print (cr.rms_error);
            std::cout << "  Peak SNR = ";
            safe_double_print (cr.PSNR);
            std::cout << "  Max error  = " << cr.maxerror;
            if (cr.maxerror != 0) {
                std::cout << " @ (" << cr.maxx << ", " << cr.maxy;
                if (img0.spec().depth > 1)
                    std::cout << ", " << cr.maxz;
                std::cout << ", " << img0.spec().channelnames[cr.maxc] << ')';
            }
            std::cout << "\n";
// when Visual Studio is used float values in scientific foramt are 
// printed with three digit exponent. We change this behaviour to fit
// Linux way
#ifdef _MSC_VER
            _set_output_format(_TWO_DIGIT_EXPONENT);
#endif
            int precis = std::cout.precision();
            std::cout << "  " << cr.nwarn << " pixels (" 
                      << std::setprecision(3) << (100.0*cr.nwarn / npels) 
                      << std::setprecision(precis) << "%) over " << warnthresh << "\n";
            std::cout << "  " << cr.nfail << " pixels (" 
                      << std::setprecision(3) << (100.0*cr.nfail / npels) 
                      << std::setprecision(precis) << "%) over " << failthresh << "\n";
            if (perceptual)
                std::cout << "  " << yee_failures << " pixels ("
                          << std::setprecision(3) << (100.0*yee_failures / npels) 
                          << std::setprecision(precis)
                          << "%) failed the perceptual test\n";

            if (cr.nfail > (failpercent/100.0 * npels) || cr.maxerror > hardfail ||
                yee_failures > (failpercent/100.0 * npels)) {
                ret = ErrFail;
            } else if (cr.nwarn > (warnpercent/100.0 * npels) || cr.maxerror > hardwarn) {
                if (ret != ErrFail)
                    ret = ErrWarn;
            }

            // If the user requested that a difference image be output,
            // do that.  N.B. we only do this for the first subimage
            // right now, because ImageBuf doesn't really know how to
            // write subimages.
            if (diffimage.size() && (cr.maxerror != 0 || !outdiffonly)) {
                ImageBuf diff (diffimage, img0.spec());
                ImageBuf::ConstIterator<float,float> pix0 (img0);
                ImageBuf::ConstIterator<float,float> pix1 (img1);
                ImageBuf::Iterator<float,float> pixdiff (diff);
                // Subtract the second image from the first.  At which
                // time we no longer need the second image, so free it.
                if (diffabs) {
                    for (  ;  pix0.valid();  ++pix0) {
                        pix1.pos (pix0.x(), pix0.y());  // ensure alignment
                        pixdiff.pos (pix0.x(), pix0.y());
                        for (int c = 0;  c < img0.nchannels();  ++c)
                            pixdiff[c] = diffscale * fabsf (pix0[c] - pix1[c]);
                    }
                } else {
                    for (  ;  pix0.valid();  ++pix0) {
                        pix1.pos (pix0.x(), pix0.y());  // ensure alignment
                        pixdiff.pos (pix0.x(), pix0.y());
                        for (int c = 0;  c < img0.spec().nchannels;  ++c)
                            pixdiff[c] = diffscale * (pix0[c] - pix1[c]);
                    }
                }

                diff.save (diffimage);

                // Clear diff image name so we only save the first
                // non-matching subimage.
                diffimage = "";
            }
        }
    }

    if (compareall && img0.nsubimages() != img1.nsubimages()) {
        std::cout << "Images had differing numbers of subimages ("
                  << img0.nsubimages() << " vs " << img1.nsubimages() << ")\n";
        ret = ErrFail;
    }
    if (!compareall && (img0.nsubimages() > 1 || img1.nsubimages() > 1)) {
        std::cout << "Only compared the first subimage (of "
                  << img0.nsubimages() << " and " << img1.nsubimages() 
                  << ", respectively)\n";
    }

    if (ret == ErrOK)
        std::cout << "PASS\n";
    else if (ret == ErrWarn)
        std::cout << "WARNING\n";
    else
        std::cout << "FAILURE\n";

    ImageCache::destroy (imagecache);
    return ret;
}
Exemple #7
0
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];
            }
        } 
    }
}
Exemple #8
0
int
OiioTool::do_action_diff (ImageRec &ir0, ImageRec &ir1,
                          Oiiotool &ot)
{
    std::cout << "Computing diff of \"" << ir0.name() << "\" vs \""
              << ir1.name() << "\"\n";
    ir0.read ();
    ir1.read ();

    int ret = DiffErrOK;
    for (int subimage = 0;  subimage < ir0.subimages();  ++subimage) {
        if (subimage > 0 && !ot.allsubimages)
            break;
        if (subimage >= ir1.subimages())
            break;

        if (ot.allsubimages) {
            std::cout << "Subimage " << subimage << ": ";
            const ImageSpec &spec (*ir0.spec(subimage));
            std::cout << spec.width << " x " << spec.height;
            if (spec.depth > 1)
                std::cout << " x " << spec.depth;
            std::cout << ", " << spec.nchannels << " channel\n";
        }

        if (ir0.miplevels(subimage) != ir1.miplevels(subimage)) {
            std::cout << "Files do not match in their number of MIPmap levels\n";
        }

        for (int m = 0;  m < ir0.miplevels(subimage);  ++m) {
            if (m > 0 && !ot.allsubimages)
                break;
            if (m > 0 && ir0.miplevels(subimage) != ir1.miplevels(subimage)) {
                std::cout << "Files do not match in their number of MIPmap levels\n";
                ret = DiffErrDifferentSize;
                break;
            }

            ImageBuf &img0 (ir0(subimage,m));
            ImageBuf &img1 (ir1(subimage,m));

            if (ot.allsubimages && ir0.miplevels(subimage) > 1) {
                std::cout << " MIP level " << m << ": ";
                std::cout << img0.spec().width << " x " << img0.spec().height;
                if (img0.spec().depth > 1)
                    std::cout << " x " << img0.spec().depth;
                std::cout << ", " << img0.spec().nchannels << " channel\n";
            }

            // Compare the dimensions of the images.  Fail if they
            // aren't the same resolution and number of channels.  No
            // problem, though, if they aren't the same data type.
            if (! same_size (img0, img1)) {
                std::cout << "Images do not match in size: ";
                std::cout << "(" << img0.spec().width << "x" << img0.spec().height;
                if (img0.spec().depth > 1)
                    std::cout << "x" << img0.spec().depth;
                std::cout << "x" << img0.spec().nchannels << ")";
                std::cout << " versus ";
                std::cout << "(" << img1.spec().width << "x" << img1.spec().height;
                if (img1.spec().depth > 1)
                    std::cout << "x" << img1.spec().depth;
                std::cout << "x" << img1.spec().nchannels << ")\n";
                ret = DiffErrDifferentSize;
                break;
            }

            int npels = img0.spec().width * img0.spec().height * img0.spec().depth;
            ASSERT (img0.spec().format == TypeDesc::FLOAT);

            // Compare the two images.
            //
            ImageBufAlgo::CompareResults cr;
            ImageBufAlgo::compare (img0, img1, ot.diff_failthresh, ot.diff_warnthresh, cr);

            int yee_failures = 0;
#if 0
            if (perceptual)
                yee_failures = ImageBufAlgo::compare_Yee (img0, img1);
#endif

            // Print the report
            //
            std::cout << "  Mean error = ";
            safe_double_print (cr.meanerror);
            std::cout << "  RMS error = ";
            safe_double_print (cr.rms_error);
            std::cout << "  Peak SNR = ";
            safe_double_print (cr.PSNR);
            std::cout << "  Max error  = " << cr.maxerror;
            if (cr.maxerror != 0) {
                std::cout << " @ (" << cr.maxx << ", " << cr.maxy;
                if (img0.spec().depth > 1)
                    std::cout << ", " << cr.maxz;
                std::cout << ", " << img0.spec().channelnames[cr.maxc] << ')';
            }
            std::cout << "\n";
// when Visual Studio is used float values in scientific foramt are 
// printed with three digit exponent. We change this behaviour to fit
// Linux way
#ifdef _MSC_VER
            _set_output_format(_TWO_DIGIT_EXPONENT);
#endif
            int precis = std::cout.precision();
            std::cout << "  " << cr.nwarn << " pixels (" 
                      << std::setprecision(3) << (100.0*cr.nwarn / npels) 
                      << std::setprecision(precis) << "%) over " << ot.diff_warnthresh << "\n";
            std::cout << "  " << cr.nfail << " pixels (" 
                      << std::setprecision(3) << (100.0*cr.nfail / npels) 
                      << std::setprecision(precis) << "%) over " << ot.diff_failthresh << "\n";
#if 0
            if (perceptual)
                std::cout << "  " << yee_failures << " pixels ("
                          << std::setprecision(3) << (100.0*yee_failures / npels) 
                          << std::setprecision(precis)
                          << "%) failed the perceptual test\n";
#endif
            if (cr.nfail > (ot.diff_failpercent/100.0 * npels) || cr.maxerror > ot.diff_hardfail ||
                yee_failures > (ot.diff_failpercent/100.0 * npels)) {
                ret = DiffErrFail;
            } else if (cr.nwarn > (ot.diff_warnpercent/100.0 * npels) || cr.maxerror > ot.diff_hardwarn) {
                if (ret != DiffErrFail)
                    ret = DiffErrWarn;
            }

#if 0
            // If the user requested that a difference image be output,
            // do that.  N.B. we only do this for the first subimage
            // right now, because ImageBuf doesn't really know how to
            // write subimages.
            if (diffimage.size() && (cr.maxerror != 0 || !outdiffonly)) {
                ImageBuf diff (diffimage, img0.spec());
                ImageBuf::ConstIterator<float,float> pix0 (img0);
                ImageBuf::ConstIterator<float,float> pix1 (img1);
                ImageBuf::Iterator<float,float> pixdiff (diff);
                // Subtract the second image from the first.  At which
                // time we no longer need the second image, so free it.
                if (diffabs) {
                    for (  ;  pix0.valid();  ++pix0) {
                        pix1.pos (pix0.x(), pix0.y());  // ensure alignment
                        pixdiff.pos (pix0.x(), pix0.y());
                        for (int c = 0;  c < img0.nchannels();  ++c)
                            pixdiff[c] = diffscale * fabsf (pix0[c] - pix1[c]);
                    }
                } else {
                    for (  ;  pix0.valid();  ++pix0) {
                        pix1.pos (pix0.x(), pix0.y());  // ensure alignment
                        pixdiff.pos (pix0.x(), pix0.y());
                        for (int c = 0;  c < img0.spec().nchannels;  ++c)
                            pixdiff[c] = diffscale * (pix0[c] - pix1[c]);
                    }
                }

                diff.save (diffimage);

                // Clear diff image name so we only save the first
                // non-matching subimage.
                diffimage = "";
            }
#endif
        }
    }

    if (ot.allsubimages && ir0.subimages() != ir1.subimages()) {
        std::cout << "Images had differing numbers of subimages ("
                  << ir0.subimages() << " vs " << ir1.subimages() << ")\n";
        ret = DiffErrFail;
    }
    if (!ot.allsubimages && (ir0.subimages() > 1 || ir1.subimages() > 1)) {
        std::cout << "Only compared the first subimage (of "
                  << ir0.subimages() << " and " << ir1.subimages() 
                  << ", respectively)\n";
    }

    if (ret == DiffErrOK)
        std::cout << "PASS\n";
    else if (ret == DiffErrWarn)
        std::cout << "WARNING\n";
    else {
        std::cout << "FAILURE\n";
    }
    return ret;
}