From 706022c6b6af991a2c465ba625ca1ac84a6fe8c4 Mon Sep 17 00:00:00 2001 From: Stefan Stavrev Date: Sat, 23 Jun 2012 20:34:17 +0200 Subject: [PATCH] Image Processing Operation: Brightness I am working in a new branch "brightness" that branches off from master. For simplicity reasons I copied the common code for the ROI class and the function parallel_image. Once approved I will remove the common code. I tested the operation for grayscale images. I am waiting for approval for the RGB algorithm which requires conversion RGB <-> HSL. Most of the code is similar to the previous operation contrast. --- src/include/imagebuf.h | 90 ++++++++++++ src/include/imagebufalgo.h | 7 + src/libOpenImageIO/imagebuf.cpp | 62 +++++++++ src/libOpenImageIO/imagebufalgo.cpp | 209 +++++++++++++++++++++++++++- src/oiiotool/oiiotool.cpp | 42 ++++++ 5 files changed, 409 insertions(+), 1 deletion(-) diff --git a/src/include/imagebuf.h b/src/include/imagebuf.h index 1f6984091c..2a219004fb 100644 --- a/src/include/imagebuf.h +++ b/src/include/imagebuf.h @@ -52,6 +52,66 @@ OIIO_NAMESPACE_ENTER { +class ImageBuf; + + + +class ROI { +public: + int xbegin, xend, ybegin, yend, zbegin, zend; + bool defined; + + // Undefined region. + ROI () : defined(false) { } + + // Region explicitly defined. + ROI (int xbegin, int xend, int ybegin, int yend, int zbegin, int zend) + : xbegin(xbegin), xend(xend), ybegin(ybegin), yend(yend), + zbegin(zbegin), zend(zend), defined(true) + { } + + // Region dimensions. + int width () const { return xend - xbegin; } + int height () const { return yend - ybegin; } + int depth () const { return zend - zbegin; } + + // Region operations. + friend ROI roi_union (const ROI &A, const ROI &B); + friend ROI roi_intersection (const ROI &A, const ROI &B); +}; + + + +/// Union of two regions, the smallest region containing both. +ROI roi_union (const ROI &A, const ROI &B); + + + +/// Intersection of two regions. +ROI roi_intersection (const ROI &A, const ROI &B); + + + +/// Return pixel data window for this ImageSpec as a ROI. +ROI get_roi (const ImageSpec &spec); + + + +/// Return full/display window for this ImageSpec as a ROI. +ROI get_roi_full (const ImageSpec &spec); + + + +/// Set pixel data window for this ImageSpec to a ROI. +void set_roi (ImageSpec &spec, const ROI &newroi); + + + +/// Set full/display window for this ImageSpec to a ROI. +void set_roi_full (ImageSpec &spec, const ROI &newroi); + + + /// An ImageBuf is a simple in-memory representation of a 2D image. It /// uses ImageInput and ImageOutput underneath for its file I/O, and has /// simple routines for setting and getting individual pixels, that @@ -424,6 +484,8 @@ class DLLPUBLIC ImageBuf { /// aren't local. void *pixeladdr (int x, int y, int z); + /// Is this ImageBuf object initialized? + bool initialized () const { return m_spec_valid || m_pixels_valid; } /// Templated class for referring to an individual pixel in an /// ImageBuf, iterating over the pixels of an ImageBuf, or iterating @@ -480,6 +542,20 @@ class DLLPUBLIC ImageBuf { m_rng_zend = std::min (zend, m_img_zend); pos (m_rng_xbegin, m_rng_ybegin, m_rng_zbegin); } + /// Construct read-write clamped valid iteration region from + /// ImageBuf and ROI. + Iterator (ImageBuf &ib, const ROI &roi) + : m_ib(&ib), m_tile(NULL) + { + init_ib (); + m_rng_xbegin = std::max (roi.xbegin, m_img_xbegin); + m_rng_xend = std::min (roi.xend, m_img_xend); + m_rng_ybegin = std::max (roi.ybegin, m_img_ybegin); + m_rng_yend = std::min (roi.yend, m_img_yend); + m_rng_zbegin = std::max (roi.zbegin, m_img_zbegin); + m_rng_zend = std::min (roi.zend, m_img_zend); + pos (m_rng_xbegin, m_rng_ybegin, m_rng_zbegin); + } /// Construct from an ImageBuf and designated region -- iterate /// over region, starting with the upper left pixel, and do NOT /// clamp the region to the valid image pixels. If "unclamped" @@ -745,6 +821,20 @@ class DLLPUBLIC ImageBuf { m_rng_zend = std::min (zend, m_img_zend); pos (m_rng_xbegin, m_rng_ybegin, m_rng_zbegin); } + /// Construct read-only clamped valid iteration region + /// from ImageBuf and ROI. + ConstIterator (ImageBuf &ib, const ROI &roi) + : m_ib(&ib), m_tile(NULL) + { + init_ib (); + m_rng_xbegin = std::max (roi.xbegin, m_img_xbegin); + m_rng_xend = std::min (roi.xend, m_img_xend); + m_rng_ybegin = std::max (roi.ybegin, m_img_ybegin); + m_rng_yend = std::min (roi.yend, m_img_yend); + m_rng_zbegin = std::max (roi.zbegin, m_img_zbegin); + m_rng_zend = std::min (roi.zend, m_img_zend); + pos (m_rng_xbegin, m_rng_ybegin, m_rng_zbegin); + } /// Construct from an ImageBuf and designated region -- iterate /// over region, starting with the upper left pixel, and do NOT /// clamp the region to the valid image pixels. If "unclamped" diff --git a/src/include/imagebufalgo.h b/src/include/imagebufalgo.h index 617a3b9497..72402c725e 100644 --- a/src/include/imagebufalgo.h +++ b/src/include/imagebufalgo.h @@ -305,6 +305,13 @@ bool DLLPUBLIC capture_image (ImageBuf &dst, int cameranum = 0, TypeDesc convert=TypeDesc::UNKNOWN); + +bool DLLPUBLIC brightness (ImageBuf &R, const ImageBuf &A, float brightness, + bool* channels_mask = NULL, ROI roi = ROI(), + int nthreads = -1); + + + }; // end namespace ImageBufAlgo diff --git a/src/libOpenImageIO/imagebuf.cpp b/src/libOpenImageIO/imagebuf.cpp index bc49e8022e..3b2e2c8247 100644 --- a/src/libOpenImageIO/imagebuf.cpp +++ b/src/libOpenImageIO/imagebuf.cpp @@ -50,6 +50,68 @@ OIIO_NAMESPACE_ENTER { + + +ROI get_roi (const ImageSpec &spec) +{ + return ROI (spec.x, spec.x + spec.width, + spec.y, spec.y + spec.height, + spec.z, spec.z + spec.depth); +} + + + +ROI get_roi_full (const ImageSpec &spec) +{ + return ROI (spec.full_x, spec.full_x + spec.full_width, + spec.full_y, spec.full_y + spec.full_height, + spec.full_z, spec.full_z + spec.full_depth); +} + + + +void set_roi (ImageSpec &spec, const ROI &newroi) +{ + spec.x = newroi.xbegin; + spec.y = newroi.ybegin; + spec.z = newroi.zbegin; + spec.width = newroi.width(); + spec.height = newroi.height(); + spec.depth = newroi.depth(); +} + + + +void set_roi_full (ImageSpec &spec, const ROI &newroi) +{ + spec.full_x = newroi.xbegin; + spec.full_y = newroi.ybegin; + spec.full_z = newroi.zbegin; + spec.full_width = newroi.width(); + spec.full_height = newroi.height(); + spec.full_depth = newroi.depth(); +} + + + +ROI roi_union (const ROI &A, const ROI &B) +{ + return ROI (std::min (A.xbegin, B.xbegin), std::max (A.xend, B.xend), + std::min (A.ybegin, B.ybegin), std::max (A.yend, B.yend), + std::min (A.zbegin, B.zbegin), std::max (A.zend, B.zend)); +} + + + +ROI roi_intersection (const ROI &A, const ROI &B) +{ + return ROI (std::max (A.xbegin, B.xbegin), std::min (A.xend, B.xend), + std::max (A.ybegin, B.ybegin), std::min (A.yend, B.yend), + std::max (A.zbegin, B.zbegin), std::min (A.zend, B.zend)); +} + + + ImageBuf::ImageBuf (const std::string &filename, ImageCache *imagecache) : m_name(filename), m_nsubimages(0), diff --git a/src/libOpenImageIO/imagebufalgo.cpp b/src/libOpenImageIO/imagebufalgo.cpp index e34ac0d265..58dbc949e2 100644 --- a/src/libOpenImageIO/imagebufalgo.cpp +++ b/src/libOpenImageIO/imagebufalgo.cpp @@ -37,6 +37,10 @@ /// \file /// Implementation of ImageBufAlgo algorithms. +#include +#include +#include + #include #include @@ -50,7 +54,7 @@ #include "dassert.h" #include "sysutil.h" #include "filter.h" - +#include "thread.h" OIIO_NAMESPACE_ENTER { @@ -1119,5 +1123,208 @@ ImageBufAlgo::fixNonFinite (ImageBuf &dst, const ImageBuf &src, } + +/// Generalized multithreading for image processing functions. +template +void +parallel_image (Func f, ImageBuf &R, ROI roi, int nthreads) +{ + // Try to fill all cores. + if (nthreads <= 0) { nthreads = 1; }//boost::thread::hardware_concurrency(); } + + if (nthreads == 0 || nthreads == 1 || R.spec().image_pixels() < 1000) { + f (roi); + } else if (nthreads > 1) { + boost::thread_group threads; + int blocksize = std::max (1, (roi.width() + nthreads - 1) / nthreads); + int roi_xend = roi.xend; + for (int i = 0; i < nthreads; i++) { + roi.xbegin += i * blocksize; + roi.xend = std::min (roi.xbegin + blocksize, roi_xend); + threads.add_thread (new boost::thread (f, roi)); + } + threads.join_all (); + } +} + + + +/// brightness = 0: don't modify the image. +/// brightness = 1: result is white image, maximum brightness. +/// brighness = -1: result is black image, minimum brightness. +/// -1 <= brightness <= 1: negative values decrease and positive +/// values increase brightness. +template +void +brightness_RA (ImageBuf &R, const ImageBuf &A, float brightness, + bool* channels_mask, ROI roi) +{ + int channels_A = A.nchannels(); + ImageBuf::ConstIterator a (A); + ImageBuf::Iterator r (R, roi); + + // If input image A has 3 channels assume RGB and apply a different + // algorithm than the one for the general case. + if (channels_A == 3) { + // RGB -> HSL + // Modify L + // HSL -> RGB + + return; + } + + // General case: channels_A != 3. + for ( ; ! r.done(); r++) { + a.pos (r.x(), r.y(), r.z()); + if (a.valid()) { + for (int c = 0; c < channels_A ; ++c) { + if (channels_mask == NULL + || (channels_mask != NULL && channels_mask[c] == true)) + r[c] = a[c] + brightness; + else + r[c] = a[c]; + } + } + } +} + + + +template +bool +brightness_R (ImageBuf &R, const ImageBuf &A, float brightness, + bool* channels_mask, ROI roi, int nthreads) +{ + switch (A.spec().format.basetype) { + case TypeDesc::FLOAT : + parallel_image (boost::bind (brightness_RA, + boost::ref(R), boost::cref(A), brightness, channels_mask, + _1), R, roi, nthreads); + return true; + case TypeDesc::UINT8 : + parallel_image (boost::bind (brightness_RA, + boost::ref(R), boost::cref(A), brightness, channels_mask, + _1), R, roi, nthreads); + return true; + case TypeDesc::INT8 : + parallel_image (boost::bind (brightness_RA, + boost::ref(R), boost::cref(A), brightness, channels_mask, + _1), R, roi, nthreads); + return true; + case TypeDesc::UINT16 : + parallel_image (boost::bind (brightness_RA, + boost::ref(R), boost::cref(A), brightness, channels_mask, + _1), R, roi, nthreads); + return true; + case TypeDesc::INT16 : + parallel_image (boost::bind (brightness_RA, + boost::ref(R), boost::cref(A), brightness, channels_mask, + _1), R, roi, nthreads); + return true; + case TypeDesc::UINT : + parallel_image (boost::bind (brightness_RA, + boost::ref(R), boost::cref(A), brightness, channels_mask, + _1), R, roi, nthreads); + return true; + case TypeDesc::INT : + parallel_image (boost::bind (brightness_RA, + boost::ref(R), boost::cref(A), brightness, channels_mask, + _1), R, roi, nthreads); + return true; + case TypeDesc::UINT64 : + parallel_image (boost::bind (brightness_RA, + boost::ref(R), boost::cref(A), brightness, channels_mask, + _1), R, roi, nthreads); + return true; + case TypeDesc::INT64 : + parallel_image (boost::bind (brightness_RA, + boost::ref(R), boost::cref(A), brightness, channels_mask, + _1), R, roi, nthreads); + return true; + case TypeDesc::HALF : + parallel_image (boost::bind (brightness_RA, + boost::ref(R), boost::cref(A), brightness, channels_mask, + _1), R, roi, nthreads); + return true; + case TypeDesc::DOUBLE : + parallel_image (boost::bind (brightness_RA, + boost::ref(R), boost::cref(A), brightness, channels_mask, + _1), R, roi, nthreads); + return true; + } + return false; +} + + + +bool +ImageBufAlgo::brightness (ImageBuf &R, const ImageBuf &A, float brightness, + bool* channels_mask, ROI roi, int nthreads) +{ + // Input image A. + const ImageSpec &specA = A.spec(); + int channels_A = specA.nchannels; + + // Output image R. + const ImageSpec &specR = R.spec(); + int channels_R = specR.nchannels; + + // The input image needs at least one channel. + if (channels_A < 1) { return false; } + + // Initialized R -> it must match A. + // Uninitialized R -> initialize from A. + if (! R.initialized()) { + R.reset ("over", specA); + } else { + if (channels_A != channels_R) { return false; } + } + + // Specified ROI -> use it. Unspecified ROI -> initialize from R. + if (! roi.defined) { + roi = get_roi (R.spec()); + } + + // Call brightness_R. + switch (R.spec().format.basetype) { + case TypeDesc::FLOAT : + return brightness_R (R, A, brightness, channels_mask, + roi, nthreads); + case TypeDesc::UINT8 : + return brightness_R (R, A, brightness, channels_mask, + roi, nthreads); + case TypeDesc::INT8 : + return brightness_R (R, A, brightness, channels_mask, + roi, nthreads); + case TypeDesc::UINT16 : + return brightness_R (R, A, brightness, channels_mask, + roi, nthreads); + case TypeDesc::INT16 : + return brightness_R (R, A, brightness, channels_mask, + roi, nthreads); + case TypeDesc::UINT : + return brightness_R (R, A, brightness, channels_mask, + roi, nthreads); + case TypeDesc::INT : + return brightness_R (R, A, brightness, channels_mask, + roi, nthreads); + case TypeDesc::UINT64 : + return brightness_R (R, A, brightness, channels_mask, + roi, nthreads); + case TypeDesc::INT64 : + return brightness_R (R, A, brightness, channels_mask, + roi, nthreads); + case TypeDesc::HALF : + return brightness_R (R, A, brightness, channels_mask, + roi, nthreads); + case TypeDesc::DOUBLE : + return brightness_R (R, A, brightness, channels_mask, + roi, nthreads); + } + return false; +} + + + } OIIO_NAMESPACE_EXIT diff --git a/src/oiiotool/oiiotool.cpp b/src/oiiotool/oiiotool.cpp index 3758142b1e..e0a8289edc 100644 --- a/src/oiiotool/oiiotool.cpp +++ b/src/oiiotool/oiiotool.cpp @@ -1396,6 +1396,47 @@ action_fixnan (int argc, const char *argv[]) +bool* +channels_mask_from_string (std::string channels_mask_string) +{ + int size = channels_mask_string.size(); + bool* channels_mask = new bool[size]; + for (int i = 0; i < size; ++i) { + channels_mask[i] = (channels_mask_string[i] == '1') ? true : false; + } + return channels_mask; +} + + + +static int +action_brightness (int argc, const char *argv[]) +{ + if (ot.postpone_callback (1, action_brightness, argc, argv)) { return 0; } + + // Get input image A. + ot.read (); + ImageRecRef A = ot.pop(); + const ImageBuf &Aib ((*A)()); + const ImageSpec &specA = Aib.spec(); + + // Get output image R. + ot.push (new ImageRec ("irec", specA, ot.imagecache)); + ImageBuf &Rib ((*ot.curimg)()); + + // Get arguments from command line. + float brightness = (float) atof(argv[1]); + std::string channels_mask_string = argv[2]; + bool* channels_mask = channels_mask_from_string (channels_mask_string); + + ImageBufAlgo::brightness (Rib, Aib, brightness, channels_mask); + + delete channels_mask; + return 0; +} + + + static void getargs (int argc, char *argv[]) { @@ -1488,6 +1529,7 @@ getargs (int argc, char *argv[]) "Convert the current image's pixels to a named color space", "--colorconvert %@ %s %s", action_colorconvert, NULL, NULL, "Convert pixels from 'src' to 'dst' color space (without regard to its previous interpretation)", + "--brightness %@ %g %s", action_brightness, NULL, NULL, "Modify image brightness", NULL); if (ap.parse(argc, (const char**)argv) < 0) {