[Pkg-phototools-commits] [SCM] openimageio branch, upstream, updated. upstream/1.1.5+dfsg0-1-g8c78cc0

Matteo F. Vescovi mfv.debian at gmail.com
Tue Feb 12 09:15:01 UTC 2013


The following commit has been merged in the upstream branch:
commit 8c78cc01cbc6fc9d4a5c038e828845e50654c7c6
Author: Matteo F. Vescovi <mfv.debian at gmail.com>
Date:   Tue Feb 12 10:12:19 2013 +0100

    Imported Upstream version 1.1.6+dfsg0

diff --git a/CHANGES b/CHANGES
index ebb2394..e8c2b5b 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,6 +1,32 @@
 Changes:
 
 
+Release 1.1.6 (11 Feb 2013)
+---------------------------
+* Fix bug that could generate NaNs or other bad values near the poles of
+  very blurry environment lookups specifically from OpenEXR latlong env maps.
+* Fix bug in oiiotool --crop where it could mis-parse the geometric parameter.
+* Fix bug in ImageCache::invalidate() where it did not properly delete the
+  fingerprint of an invalidated file.
+* Cleanup and fixes in the oiiotool command line help messages.
+* New function ImageBufAlgo::paste() copies a region from one IB to another.
+* oiiotool --fit resizes an image to fit into a given resolution (keeping the
+  original aspect ratio and padding with black if needed).
+* ImageBufAlgo::channels() and "oiiotool --ch" have been extended to allow
+  new channels (specified to be filled with numeric constants) to also be
+  named.
+* New function ImageBufAlgo::mul() and "oiiotool --cmul" let you multiply
+  an image by a scalar constant (or per-channel constant).
+* Important maketx bug fix: when creating latlong environment maps as 
+  OpenEXR files, it was adding energy near the poles, making low-res
+  MIP levels too bright near the poles.
+* Fix to "oiiotool --text" and "oiiotool --fill" -- both were
+  incorrectly making the base image black rather than drawing overtop of
+  the previous image.
+* Fix FreeBSD compile when not using TBB.
+* New oiiotool --swap exchanges the top two items on the image stack.
+
+
 Release 1.1.5 (29 Jan 2013)
 ---------------------------
 * Bug fix in ImageBufAlgo::parallel_image utility template -- care when
@@ -11,6 +37,7 @@ Release 1.1.5 (29 Jan 2013)
   treat z=0 pixels as infinitely far away, not super close.  You can turn
   this on from oiiotool with:  oiiotool --zover:zeroisinf=1 ...
 
+
 Release 1.1.4 (27 Jan 2013)
 ---------------------------
 * ImageBufAlgo::make_texture() allows you to do the same thing that
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 42523b4..d9aa5b9 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -3,7 +3,7 @@ project (OpenImageIO)
 # Release version of the library
 set (OIIO_VERSION_MAJOR 1)
 set (OIIO_VERSION_MINOR 1)
-set (OIIO_VERSION_PATCH 5)
+set (OIIO_VERSION_PATCH 6)
 
 cmake_minimum_required (VERSION 2.6)
 if (NOT CMAKE_VERSION VERSION_LESS 2.8.4)
diff --git a/src/cmake/platform.cmake b/src/cmake/platform.cmake
index 768c645..df778c9 100644
--- a/src/cmake/platform.cmake
+++ b/src/cmake/platform.cmake
@@ -23,6 +23,13 @@ if (UNIX)
     elseif (${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD")
         set (platform "FreeBSD")
         set (CXXFLAGS "${CXXFLAGS} -DFREEBSD")
+        if (${CMAKE_SYSTEM_PROCESSOR} STREQUAL "i386")
+            if (NOT USE_TBB)
+                # to use gcc atomics we need cpu instructions only available
+                # with arch of i586 or higher
+                set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=i586")
+            endif()
+        endif()
     else ()
         string (TOLOWER ${CMAKE_SYSTEM_NAME} platform)
     endif ()
diff --git a/src/doc/oiiotool.tex b/src/doc/oiiotool.tex
index fe7c510..ed83d12 100644
--- a/src/doc/oiiotool.tex
+++ b/src/doc/oiiotool.tex
@@ -189,17 +189,41 @@ Convert a scanline file to a tiled file with $16 \times 16$ tiles:
 \begin{code}
     oiiotool foo.jpg --caption "Hawaii vacation" -o bar.jpg
     oiiotool foo.jpg --keyword "volcano,lava" -o bar.jpg
+    oiiotool in.exr --attrib "FStop" 22.0 -o out.exr
 \end{code}
 
 
+\subsection*{Scale the values in an image}
+
+Reduce the brightness of the R, G, and B channels by 10%%,
+but leave the A channel at its original value:
+
+\begin{code}
+    oiiotool original.exr --cmul 0.9,0.9,0.9,1.0 -o out.exr
+\end{code}
+
 \subsection*{Resize an image}
 
+Resize by a known scale factor:
+
 \begin{code}
     oiiotool original.tif --resize 200% -o big.tif
     oiiotool original.tif --resize 25% -o small.tif
+\end{code}
+
+\noindent Resize to a specific resolution:
+
+\begin{code}
     oiiotool original.tif --resize 1024x768 -o specific.tif
 \end{code}
 
+\noindent Resize to fit into a given resolution, keeping the original
+aspect ratio and padding with black where necessary:
+
+\begin{code}
+    oiiotool original.tif --fit 640x480 -o fit.tif
+\end{code}
+
 
 \subsection*{Color convert an image}
 
@@ -253,6 +277,12 @@ image, and add an alpha channel that is 1 everywhere:
     oiiotool allmyaovs.exr --ch spec.R,spec.G,spec.B,=1 -o spec.exr
 \end{code}
 
+\noindent Add a channel to an RGBA image, setting it to 3.0 everywhere,
+and naming it ``Z'' so it will be recognized as a $z$ channel:
+\begin{code}
+    oiiotool rgba.exr --ch R,G,B,A,Z=3.0 -o rgbaz.exr
+\end{code}
+
 
 
 \newpage
@@ -590,14 +620,19 @@ Note that this results in both the current and the next image
 on the stack being identical copies.
 \apiend
 
+\apiitem{--swap}
+\NEW
+Swap the current image and the next one on the stack.
+\apiend
+
 \apiitem{--ch {\rm \emph{channellist}}}
 Replaces the top image with a new copy whose channels have been 
 reordered as given by the \emph{channellist}.  The {\cf channellist}
 is a comma-separated list of channel names and/or numbers (e.g., 
-\qkw{R,G,B}, \emph{A}, \emph{B,G,R}, \emph{4,5,6,A}).  Channel numbers outside
+\qkw{R,G,B}, \qkw{A}, \qkw{B,G,R}, \qkw{4,5,6,A}).  Channel numbers outside
 the valid range of input channels, or unknown names, will be replaced
 by black channels.
-A channel designation beginning with the {\cf =} character and followed
+\NEW A channel designation beginning with the {\cf =} character and followed
 by a number is a \emph{literal value} that will be used to fill that
 channel.  If the \emph{channellist} is shorter than the number of
 channels in the source image, unspecified channels will be omitted.
@@ -720,6 +755,16 @@ Replace the current image with a new image that has each pixel
 consisting of the \emph{absolute value} of he old pixel value.
 \apiend
 
+\apiitem{--cmul {\rm \emph{value}} \\
+--cmul {\rm \emph{value0,value1,value2...}}}
+\NEW
+Multiply all the pixel values in the top image by a constant value.
+If a single constant value is given, all color channels will have their values
+multiplied by the same value.  Alternatively, a series of
+comma-separated constant values (with no spaces!) may be used to specifiy a
+different multiplier for each channel in the image, respectively.
+\apiend
+
 \apiitem{--over}
 \index{composite}
 Replace the \emph{two} top images with a new image that is the
@@ -791,7 +836,7 @@ the full/display window.
 
 \apiitem{--resize {\rm \emph{size}}}
 Replace the current image with a new image that is resized to the 
-given pixel data resolution.  The size is in the form 
+given pixel data resolution.  The \emph{size} is in the form 
 \\ \spc\spc \emph{width}\,{\cf x}\,\emph{height}
 \\ or~~~~ \spc \emph{scale}{\verb|%|} \\
 
@@ -806,13 +851,31 @@ decreasing resolution. \\
 \noindent Examples: 
 
 \begin{tabular}{p{2in} p{4in}}
-    {\cf --resize 1024x768}  &     new resolution w=100, h=120, offset x=35, y=40 \\
+    {\cf --resize 1024x768}  &     new resolution w=100, h=120 \\
     {\cf --resize 50{\verb|%|}}  & reduce resolution to 50\verb|%| \\
     {\cf --resize 300{\verb|%|}}  & increase resolution by 3x
 \end{tabular}
 
 \apiend
 
+\apiitem{--fit {\rm \emph{size}}}
+\NEW
+Replace the current image with a new image that is resized to fit
+into the given pixel data resolution, keeping the original aspect ratio
+and padding with black pixels if the requested image size does not
+have the same aspect ratio.  The \emph{size} is in the form 
+\\ \spc\spc \emph{width}\,{\cf x}\,\emph{height}
+\\ or~~~~ \spc \emph{width}\,{\cf x}\,\emph{height}{\cf [+-]}\emph{xorigin}{\cf [+-]}\emph{yorigin} \\
+
+Optional appended arguments include:
+
+\begin{tabular}{p{10pt} p{1in} p{3.75in}}
+ & {\cf filter=}\emph{name} & Filter name. The default is {\cf
+  blackman-harris} when increasing resolution, {\cf lanczos3} when
+decreasing resolution. \\
+\end{tabular}
+\apiend
+
 \apiitem{--fixnan {\rm \emph{streategy}}}
 Replace the new image with a copy in which any pixels that contained
 {\cf NaN} or {\cf Inf} values (hereafter referred to collectively as
diff --git a/src/doc/openimageio.tex b/src/doc/openimageio.tex
index 2f2a299..7d237b8 100644
--- a/src/doc/openimageio.tex
+++ b/src/doc/openimageio.tex
@@ -85,7 +85,7 @@
 \date{{\large 
 %Editor: Larry Gritz \\[2ex]
 Date: 18 Nov, 2012
-\\ (with corrections, 29 Jan 2013)
+\\ (with corrections, 5 Feb 2013)
 }}
 
 
diff --git a/src/include/imagebufalgo.h b/src/include/imagebufalgo.h
index afbc3a7..fe5cb4c 100644
--- a/src/include/imagebufalgo.h
+++ b/src/include/imagebufalgo.h
@@ -124,12 +124,17 @@ bool OIIO_API setNumChannels(ImageBuf &dst, const ImageBuf &src, int numChannels
 
 /// Generic channel shuffling -- copy src to dst, but with channels in
 /// the order channelorder[0..nchannels-1].  Does not support in-place
-/// operation.  If channelorder[i] < 0, it will just make dst channel i
-/// be black (0.0) rather than copying from src.
-///
+/// operation.  For any channel in which channelorder[i] < 0, it will
+/// just make dst channel i a constant color -- set to channelvalues[i]
+/// (if channelvalues != NULL) or 0.0 (if channelvalues == NULL).
+//
 /// If channelorder is NULL, it will be interpreted as
-/// {0, 1, ..., nchannels-1}.
+/// {0, 1, ..., nchannels-1} (meaning that it's only renaming channels,
+/// not reordering them.
 ///
+/// If newchannelnames is not NULL, it points to an array of new channel
+/// names.  Channels for which newchannelnames[i] is the empty string (or
+/// all channels, if newchannelnames == NULL) will be named as follows:
 /// If shuffle_channel_names is false, the resulting dst image will have
 /// default channel names in the usual order ("R", "G", etc.), but if
 /// shuffle_channel_names is true, the names will be taken from the
@@ -137,8 +142,15 @@ bool OIIO_API setNumChannels(ImageBuf &dst, const ImageBuf &src, int numChannels
 /// shuffling both channel ordering and their names could result in no
 /// semantic change at all, if you catch the drift.
 bool OIIO_API channels (ImageBuf &dst, const ImageBuf &src,
+                        int nchannels, const int *channelorder,
+                        const float *channelvalues=NULL,
+                        const std::string *newchannelnames=NULL,
+                        bool shuffle_channel_names=false);
+
+/// DEPRECATED -- for back-compatibility
+bool OIIO_API channels (ImageBuf &dst, const ImageBuf &src,
                          int nchannels, const int *channelorder,
-                         bool shuffle_channel_names=false);
+                         bool shuffle_channel_names);
 
 /// Make dst be a cropped copy of src, but with the new pixel data
 /// window range [xbegin..xend) x [ybegin..yend).  Source pixel data
@@ -151,6 +163,14 @@ bool OIIO_API crop (ImageBuf &dst, const ImageBuf &src,
                      const float *bordercolor=NULL);
 
 
+/// Copy into dst, beginning at (xbegin,ybegin,zbegin), the pixels of
+/// src described by srcroi.  If srcroi is ROI(), the entirety of src
+/// will be used.  It will copy into channels [chbegin...], as many
+/// channels as are described by srcroi.
+bool OIIO_API paste (ImageBuf &dst, int xbegin, int ybegin,
+                     int zbegin, int chbegin,
+                     const ImageBuf &src, ROI srcroi=ROI());
+
 
 /// Add the pixels of two images A and B, putting the sum in dst.
 /// The 'options' flag controls behaviors, particular of what happens
@@ -172,6 +192,18 @@ enum OIIO_API AddOptions
 };
 
 
+/// For all pixels of R within region roi (defaulting to all the defined
+/// pixels in R), multiply their value by 'val'.  Use the given number
+/// of threads.
+bool OIIO_API mul (ImageBuf &R, float val, ROI roi=ROI(), int threads=0);
+
+/// For all pixels of R within region roi (defaulting to all the defined
+/// pixels in R), multiply their value by val[0..nchans-1]. Use the
+/// given number of threads.
+bool OIIO_API mul (ImageBuf &R, const float *val, ROI roi=ROI(), int threads=0);
+
+
+
 /// Apply a color transform to the pixel values
 ///
 /// In-place operations (dst == src) are supported
@@ -595,6 +627,39 @@ parallel_image (Func f, ROI roi, int nthreads=0)
 }
 
 
+
+// Macro to call a type-specialzed version func<type>(R,...)
+#define OIIO_DISPATCH_TYPES(name,func,type,R,...)                       \
+    switch (type.basetype) {                                            \
+    case TypeDesc::FLOAT :                                              \
+        return func<float> (R, __VA_ARGS__); break;                     \
+    case TypeDesc::UINT8 :                                              \
+        return func<unsigned char> (R, __VA_ARGS__); break;             \
+    case TypeDesc::HALF  :                                              \
+        return func<half> (R, __VA_ARGS__); break;                      \
+    case TypeDesc::UINT16:                                              \
+        return func<unsigned short> (R, __VA_ARGS__); break;            \
+    case TypeDesc::INT8  :                                              \
+        return func<char> (R, __VA_ARGS__); break;                      \
+    case TypeDesc::INT16 :                                              \
+        return func<short> (R, __VA_ARGS__); break;                     \
+    case TypeDesc::UINT  :                                              \
+        return func<unsigned int> (R, __VA_ARGS__); break;              \
+    case TypeDesc::INT   :                                              \
+        return func<int> (R, __VA_ARGS__); break;                       \
+    case TypeDesc::UINT64:                                              \
+        return func<unsigned long long> (R, __VA_ARGS__); break;        \
+    case TypeDesc::INT64 :                                              \
+        return func<long long> (R, __VA_ARGS__); break;                 \
+    case TypeDesc::DOUBLE:                                              \
+        return func<double> (R, __VA_ARGS__); break;                    \
+    default:                                                            \
+        (R).error ("%s: Unsupported pixel data format '%s'", name, type); \
+        return false;                                                   \
+    }
+
+
+
 };  // end namespace ImageBufAlgo
 
 
diff --git a/src/include/thread.h b/src/include/thread.h
index 5682f9b..92267f8 100644
--- a/src/include/thread.h
+++ b/src/include/thread.h
@@ -98,9 +98,9 @@
 #endif
 
 #if defined(__GNUC__) && (defined(_GLIBCXX_ATOMIC_BUILTINS) || (__GNUC__ * 100 + __GNUC_MINOR__ >= 401))
-#if !defined(__FreeBSD__) || defined(__x86_64__)
-#define USE_GCC_ATOMICS
-#endif
+# if !USE_TBB
+# define USE_GCC_ATOMICS 1
+# endif
 #endif
 
 OIIO_NAMESPACE_ENTER
diff --git a/src/libOpenImageIO/imagebufalgo.cpp b/src/libOpenImageIO/imagebufalgo.cpp
index 961c787..0e70f0b 100644
--- a/src/libOpenImageIO/imagebufalgo.cpp
+++ b/src/libOpenImageIO/imagebufalgo.cpp
@@ -62,7 +62,6 @@
 #endif
 
 
-
 OIIO_NAMESPACE_ENTER
 {
 
@@ -70,7 +69,7 @@ namespace
 {
 
 template<typename T>
-static inline void
+static inline bool
 fill_ (ImageBuf &dst, const float *values, ROI roi=ROI())
 {
     int chbegin = roi.chbegin;
@@ -78,6 +77,7 @@ fill_ (ImageBuf &dst, const float *values, ROI roi=ROI())
     for (ImageBuf::Iterator<T> p (dst, roi);  !p.done();  ++p)
         for (int c = chbegin, i = 0;  c < chend;  ++c, ++i)
             p[c] = values[i];
+    return true;
 }
 
 }
@@ -86,23 +86,7 @@ bool
 ImageBufAlgo::fill (ImageBuf &dst, const float *pixel, ROI roi)
 {
     ASSERT (pixel && "fill must have a non-NULL pixel value pointer");
-    switch (dst.spec().format.basetype) {
-    case TypeDesc::FLOAT : fill_<float> (dst, pixel, roi); break;
-    case TypeDesc::UINT8 : fill_<unsigned char> (dst, pixel, roi); break;
-    case TypeDesc::UINT16: fill_<unsigned short> (dst, pixel, roi); break;
-    case TypeDesc::HALF  : fill_<half> (dst, pixel, roi); break;
-    case TypeDesc::INT8  : fill_<char> (dst, pixel, roi); break;
-    case TypeDesc::INT16 : fill_<short> (dst, pixel, roi); break;
-    case TypeDesc::UINT  : fill_<unsigned int> (dst, pixel, roi); break;
-    case TypeDesc::INT   : fill_<int> (dst, pixel, roi); break;
-    case TypeDesc::UINT64: fill_<unsigned long long> (dst, pixel, roi); break;
-    case TypeDesc::INT64 : fill_<long long> (dst, pixel, roi); break;
-    case TypeDesc::DOUBLE: fill_<double> (dst, pixel, roi); break;
-    default:
-        dst.error ("Unsupported pixel data format '%s'", dst.spec().format);
-        return false;
-    }
-    
+    OIIO_DISPATCH_TYPES ("fill", fill_, dst.spec().format, dst, pixel, roi);
     return true;
 }
 
@@ -144,6 +128,84 @@ ImageBufAlgo::checker (ImageBuf &dst,
 namespace {
 
 template<class T>
+bool paste_ (ImageBuf &dst, int xbegin, int ybegin,
+             int zbegin, int chbegin,
+             const ImageBuf &src, ROI srcroi)
+{
+    const ImageSpec &dstspec (dst.spec());
+    if (dstspec.format.basetype != TypeDesc::FLOAT) {
+        dst.error ("paste: only 'float' destination images are supported");
+        return false;
+    }
+
+    ImageBuf::ConstIterator<T,float> s (src, srcroi.xbegin, srcroi.xend,
+                                        srcroi.ybegin, srcroi.yend,
+                                        srcroi.zbegin, srcroi.zend, true);
+    ImageBuf::Iterator<float,float> d (dst, xbegin, xbegin+srcroi.width(),
+                                       ybegin, ybegin+srcroi.height(),
+                                       zbegin, zbegin+srcroi.depth(), true);
+    int src_nchans = src.nchannels ();
+    int dst_nchans = dst.nchannels ();
+    for ( ;  ! s.done();  ++s, ++d) {
+        if (! d.exists())
+            continue;  // Skip paste-into pixels that don't overlap dst's data
+        if (s.exists()) {
+            for (int c = srcroi.chbegin, c_dst = chbegin;
+                   c < srcroi.chend;  ++c, ++c_dst) {
+                if (c_dst >= 0 && c_dst < dst_nchans)
+                    d[c_dst] = c < src_nchans ? s[c] : 0.0f;
+            }
+        } else {
+            // Copying from outside src's data -- black
+            for (int c = srcroi.chbegin, c_dst = chbegin;
+                   c < srcroi.chend;  ++c, ++c_dst) {
+                if (c_dst >= 0 && c_dst < dst_nchans)
+                    d[c_dst] = 0.0f;
+            }
+        }
+    }
+    return true;
+}
+
+};  // anon namespace
+
+
+
+bool
+ImageBufAlgo::paste (ImageBuf &dst, int xbegin, int ybegin,
+                     int zbegin, int chbegin,
+                     const ImageBuf &src, ROI srcroi)
+{
+    if (! srcroi.defined())
+        srcroi = get_roi(src.spec());
+
+    // If dst is uninitialized, size it like the region
+    if (!dst.initialized()) {
+        std::cerr << "Allocating space\n";
+        ImageSpec dst_spec = src.spec();
+        dst_spec.x = srcroi.xbegin;
+        dst_spec.y = srcroi.ybegin;
+        dst_spec.z = srcroi.zbegin;
+        dst_spec.width = srcroi.width();
+        dst_spec.height = srcroi.height();
+        dst_spec.depth = srcroi.depth();
+        dst_spec.nchannels = srcroi.nchannels();
+        dst_spec.set_format (TypeDesc::FLOAT);
+        dst.alloc (dst_spec);
+    }
+
+    // do the actual copying
+    OIIO_DISPATCH_TYPES ("paste", paste_, src.spec().format,
+                         dst, xbegin, ybegin, zbegin, chbegin, src, srcroi);
+    return false;
+}
+
+
+
+
+namespace {
+
+template<class T>
 bool crop_ (ImageBuf &dst, const ImageBuf &src,
             int xbegin, int xend, int ybegin, int yend,
             const float *bordercolor)
@@ -194,47 +256,8 @@ ImageBufAlgo::crop (ImageBuf &dst, const ImageBuf &src,
     if (!dst.pixels_valid())
         dst.alloc (dst_spec);
 
-    // do the actual copying
-    switch (src.spec().format.basetype) {
-    case TypeDesc::FLOAT :
-        return crop_<float> (dst, src, xbegin, xend, ybegin, yend, bordercolor);
-        break;
-    case TypeDesc::UINT8 :
-        return crop_<unsigned char> (dst, src, xbegin, xend, ybegin, yend, bordercolor);
-        break;
-    case TypeDesc::INT8  :
-        return crop_<char> (dst, src, xbegin, xend, ybegin, yend, bordercolor);
-        break;
-    case TypeDesc::UINT16:
-        return crop_<unsigned short> (dst, src, xbegin, xend, ybegin, yend, bordercolor);
-        break;
-    case TypeDesc::INT16 :
-        return crop_<short> (dst, src, xbegin, xend, ybegin, yend, bordercolor);
-        break;
-    case TypeDesc::UINT  :
-        return crop_<unsigned int> (dst, src, xbegin, xend, ybegin, yend, bordercolor);
-        break;
-    case TypeDesc::INT   :
-        return crop_<int> (dst, src, xbegin, xend, ybegin, yend, bordercolor);
-        break;
-    case TypeDesc::UINT64:
-        return crop_<unsigned long long> (dst, src, xbegin, xend, ybegin, yend, bordercolor);
-        break;
-    case TypeDesc::INT64 :
-        return crop_<long long> (dst, src, xbegin, xend, ybegin, yend, bordercolor);
-        break;
-    case TypeDesc::HALF  :
-        return crop_<half> (dst, src, xbegin, xend, ybegin, yend, bordercolor);
-        break;
-    case TypeDesc::DOUBLE:
-        return crop_<double> (dst, src, xbegin, xend, ybegin, yend, bordercolor);
-        break;
-    default:
-        dst.error ("Unsupported pixel data format '%s'", src.spec().format);
-        return false;
-    }
-    
-    ASSERT (0);
+    OIIO_DISPATCH_TYPES ("crop", crop_, src.spec().format,
+                         dst, src, xbegin, xend, ybegin, yend, bordercolor);
     return false;
 }
 
@@ -246,6 +269,20 @@ ImageBufAlgo::channels (ImageBuf &dst, const ImageBuf &src,
                         int nchannels, const int *channelorder,
                         bool shuffle_channel_names)
 {
+    // DEPRECATED -- just provide link compatibility
+    return channels (dst, src, nchannels, channelorder, NULL, NULL,
+                     shuffle_channel_names);
+}
+
+
+
+bool
+ImageBufAlgo::channels (ImageBuf &dst, const ImageBuf &src,
+                        int nchannels, const int *channelorder,
+                        const float *channelvalues,
+                        const std::string *newchannelnames,
+                        bool shuffle_channel_names)
+{
     // Not intended to create 0-channel images.
     if (nchannels <= 0) {
         dst.error ("%d-channel images not supported", nchannels);
@@ -280,19 +317,30 @@ ImageBufAlgo::channels (ImageBuf &dst, const ImageBuf &src,
     ImageSpec newspec = src.spec();
     newspec.nchannels = nchannels;
     newspec.default_channel_names ();
-    if (shuffle_channel_names) {
-        newspec.alpha_channel = -1;
-        newspec.z_channel = -1;
-        for (int c = 0; c < nchannels;  ++c) {
-            int csrc = channelorder[c];
-            if (csrc >= 0 && csrc < src.spec().nchannels) {
-                newspec.channelnames[c] = src.spec().channelnames[csrc];
-                if (csrc == src.spec().alpha_channel)
-                    newspec.alpha_channel = c;
-                if (csrc == src.spec().z_channel)
-                    newspec.z_channel = c;
-            }
-        }
+    newspec.alpha_channel = -1;
+    newspec.z_channel = -1;
+    for (int c = 0; c < nchannels;  ++c) {
+        int csrc = channelorder[c];
+        // If the user gave an explicit name for this channel, use it...
+        if (newchannelnames && newchannelnames[c].size())
+            newspec.channelnames[c] = newchannelnames[c];
+        // otherwise, if shuffle_channel_names, use the channel name of
+        // the src channel we're using (otherwise stick to the default name)
+        else if (shuffle_channel_names &&
+                 csrc >= 0 && csrc < src.spec().nchannels)
+            newspec.channelnames[c] = src.spec().channelnames[csrc];
+        // otherwise, use the name of the source in that slot
+        else if (csrc >= 0 && csrc < src.spec().nchannels)
+            newspec.channelnames[c] = src.spec().channelnames[c];
+        // Use the names (or designation of the src image, if
+        // shuffle_channel_names is true) to deduce the alpha and z channels.
+        if ((shuffle_channel_names && csrc == src.spec().alpha_channel) ||
+              Strutil::iequals (newspec.channelnames[c], "A") ||
+              Strutil::iequals (newspec.channelnames[c], "alpha"))
+            newspec.alpha_channel = c;
+        if ((shuffle_channel_names && csrc == src.spec().z_channel) ||
+              Strutil::iequals (newspec.channelnames[c], "Z"))
+            newspec.z_channel = c;
     }
 
     // Update the image (realloc with the new spec)
@@ -307,6 +355,7 @@ ImageBufAlgo::channels (ImageBuf &dst, const ImageBuf &src,
     char *pixels = (char *) dst.pixeladdr (dst.xbegin(), dst.ybegin(),
                                            dst.zbegin());
     for (int c = 0;  c < nchannels;  ++c) {
+        // Copy shuffled channels
         if (channelorder[c] >= 0 && channelorder[c] < src.spec().nchannels) {
             int csrc = channelorder[c];
             src.get_pixel_channels (src.xbegin(), src.xend(),
@@ -315,6 +364,13 @@ ImageBufAlgo::channels (ImageBuf &dst, const ImageBuf &src,
                                     csrc, csrc+1, newspec.format, pixels,
                                     dstxstride, dstystride, dstzstride);
         }
+        // Set channels that are literals
+        if (channelorder[c] < 0 && channelvalues && channelvalues[c]) {
+            ROI roi = get_roi (dst.spec());
+            roi.chbegin = c;
+            roi.chend = c+1;
+            ImageBufAlgo::fill (dst, &channelvalues[c], roi);
+        }
         pixels += channelsize;
     }
     return true;
@@ -458,6 +514,54 @@ ImageBufAlgo::add (ImageBuf &dst, const ImageBuf &A, const ImageBuf &B,
 }
 
 
+
+namespace {
+
+template<class Rtype>
+static bool
+mul_impl (ImageBuf &R, const float *val, ROI roi, int nthreads)
+{
+    if (nthreads == 1 || roi.npixels() < 1000) {
+        // For-sure single thread case
+        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];
+    } else {
+        // Possible multiple thread case -- recurse via parallel_image
+        ImageBufAlgo::parallel_image (boost::bind(mul_impl<Rtype>,
+                                                  boost::ref(R), val, _1, 1),
+                                      roi, nthreads);
+    }
+    return true;
+}
+
+
+} // anon namespace
+
+
+bool
+ImageBufAlgo::mul (ImageBuf &R, const float *val, ROI roi, int nthreads)
+{
+    roi.chend = std::min (roi.chend, R.nchannels()); // clamp
+    OIIO_DISPATCH_TYPES ("mul", mul_impl, R.spec().format,
+                         R, val, roi, nthreads);
+    return true;
+}
+
+
+
+bool
+ImageBufAlgo::mul (ImageBuf &R, float val, ROI roi, int nthreads)
+{
+    int nc = R.nchannels();
+    float *vals = ALLOCA (float, nc);
+    for (int c = 0;  c < nc;  ++c)
+        vals[c] = val;
+    return mul (R, vals, roi, nthreads);
+}
+
+
+
 bool
 ImageBufAlgo::computePixelStats (PixelStats &stats, const ImageBuf &src)
 {
@@ -744,22 +848,8 @@ isConstantColor_ (const ImageBuf &src, float *color)
 bool
 ImageBufAlgo::isConstantColor (const ImageBuf &src, float *color)
 {
-    switch (src.spec().format.basetype) {
-    case TypeDesc::FLOAT : return isConstantColor_<float> (src, color); break;
-    case TypeDesc::UINT8 : return isConstantColor_<unsigned char> (src, color); break;
-    case TypeDesc::INT8  : return isConstantColor_<char> (src, color); break;
-    case TypeDesc::UINT16: return isConstantColor_<unsigned short> (src, color); break;
-    case TypeDesc::INT16 : return isConstantColor_<short> (src, color); break;
-    case TypeDesc::UINT  : return isConstantColor_<unsigned int> (src, color); break;
-    case TypeDesc::INT   : return isConstantColor_<int> (src, color); break;
-    case TypeDesc::UINT64: return isConstantColor_<unsigned long long> (src, color); break;
-    case TypeDesc::INT64 : return isConstantColor_<long long> (src, color); break;
-    case TypeDesc::HALF  : return isConstantColor_<half> (src, color); break;
-    case TypeDesc::DOUBLE: return isConstantColor_<double> (src, color); break;
-    default:
-        src.error ("Unsupported pixel data format '%s'", src.spec().format);
-        return false;
-    }
+    OIIO_DISPATCH_TYPES ("isConstantColor", isConstantColor_,
+                         src.spec().format, src, color);
 };
 
 
@@ -782,22 +872,8 @@ isConstantChannel_ (const ImageBuf &src, int channel, float val)
 bool
 ImageBufAlgo::isConstantChannel (const ImageBuf &src, int channel, float val)
 {
-    switch (src.spec().format.basetype) {
-    case TypeDesc::FLOAT : return isConstantChannel_<float> (src, channel, val); break;
-    case TypeDesc::UINT8 : return isConstantChannel_<unsigned char> (src, channel, val); break;
-    case TypeDesc::INT8  : return isConstantChannel_<char> (src, channel, val); break;
-    case TypeDesc::UINT16: return isConstantChannel_<unsigned short> (src, channel, val); break;
-    case TypeDesc::INT16 : return isConstantChannel_<short> (src, channel, val); break;
-    case TypeDesc::UINT  : return isConstantChannel_<unsigned int> (src, channel, val); break;
-    case TypeDesc::INT   : return isConstantChannel_<int> (src, channel, val); break;
-    case TypeDesc::UINT64: return isConstantChannel_<unsigned long long> (src, channel, val); break;
-    case TypeDesc::INT64 : return isConstantChannel_<long long> (src, channel, val); break;
-    case TypeDesc::HALF  : return isConstantChannel_<half> (src, channel, val); break;
-    case TypeDesc::DOUBLE: return isConstantChannel_<double> (src, channel, val); break;
-    default:
-        src.error ("Unsupported pixel data format '%s'", src.spec().format);
-        return false;
-    }
+    OIIO_DISPATCH_TYPES ("isConstantChannel", isConstantChannel_,
+                         src.spec().format, src, channel, val);
 };
 
 namespace
@@ -805,7 +881,7 @@ namespace
 
 template<typename T>
 static inline bool
-isMonochrome_ (const ImageBuf &src)
+isMonochrome_ (const ImageBuf &src, int dummy)
 {
     int nchannels = src.nchannels();
     if (nchannels < 2) return true;
@@ -829,22 +905,8 @@ isMonochrome_ (const ImageBuf &src)
 bool
 ImageBufAlgo::isMonochrome(const ImageBuf &src)
 {
-    switch (src.spec().format.basetype) {
-    case TypeDesc::FLOAT : return isMonochrome_<float> (src); break;
-    case TypeDesc::UINT8 : return isMonochrome_<unsigned char> (src); break;
-    case TypeDesc::INT8  : return isMonochrome_<char> (src); break;
-    case TypeDesc::UINT16: return isMonochrome_<unsigned short> (src); break;
-    case TypeDesc::INT16 : return isMonochrome_<short> (src); break;
-    case TypeDesc::UINT  : return isMonochrome_<unsigned int> (src); break;
-    case TypeDesc::INT   : return isMonochrome_<int> (src); break;
-    case TypeDesc::UINT64: return isMonochrome_<unsigned long long> (src); break;
-    case TypeDesc::INT64 : return isMonochrome_<long long> (src); break;
-    case TypeDesc::HALF  : return isMonochrome_<half> (src); break;
-    case TypeDesc::DOUBLE: return isMonochrome_<double> (src); break;
-    default:
-        src.error ("Unsupported pixel data format '%s'", src.spec().format);
-        return false;
-    }
+    OIIO_DISPATCH_TYPES ("isMonochrome", isMonochrome_, src.spec().format,
+                         src, 0);
 };
 
 std::string
@@ -1068,35 +1130,8 @@ ImageBufAlgo::resize (ImageBuf &dst, const ImageBuf &src,
                       int xbegin, int xend, int ybegin, int yend,
                       Filter2D *filter)
 {
-    switch (src.spec().format.basetype) {
-    case TypeDesc::FLOAT :
-        return resize_<float> (dst, src, xbegin, xend, ybegin, yend, filter);
-    case TypeDesc::UINT8 :
-        return resize_<unsigned char> (dst, src, xbegin, xend, ybegin, yend, filter);
-    case TypeDesc::INT8  :
-        return resize_<char> (dst, src, xbegin, xend, ybegin, yend, filter);
-    case TypeDesc::UINT16:
-        return resize_<unsigned short> (dst, src, xbegin, xend, ybegin, yend, filter);
-    case TypeDesc::INT16 :
-        return resize_<short> (dst, src, xbegin, xend, ybegin, yend, filter);
-    case TypeDesc::UINT  :
-        return resize_<unsigned int> (dst, src, xbegin, xend, ybegin, yend, filter);
-    case TypeDesc::INT   :
-        return resize_<int> (dst, src, xbegin, xend, ybegin, yend, filter);
-    case TypeDesc::UINT64:
-        return resize_<unsigned long long> (dst, src, xbegin, xend, ybegin, yend, filter);
-    case TypeDesc::INT64 :
-        return resize_<long long> (dst, src, xbegin, xend, ybegin, yend, filter);
-    case TypeDesc::HALF  :
-        return resize_<half> (dst, src, xbegin, xend, ybegin, yend, filter);
-    case TypeDesc::DOUBLE:
-        return resize_<double> (dst, src, xbegin, xend, ybegin, yend, filter);
-    default:
-        dst.error ("Unsupported pixel data format '%s'", src.spec().format);
-        return false;
-    }
-
-    ASSERT (0);
+    OIIO_DISPATCH_TYPES ("resize", resize_, src.spec().format,
+                         dst, src, xbegin, xend, ybegin, yend, filter);
     return false;
 }
 
diff --git a/src/libOpenImageIO/imagebufalgo_test.cpp b/src/libOpenImageIO/imagebufalgo_test.cpp
index a4c2c6b..8ac29c9 100644
--- a/src/libOpenImageIO/imagebufalgo_test.cpp
+++ b/src/libOpenImageIO/imagebufalgo_test.cpp
@@ -149,6 +149,49 @@ void test_crop ()
 
 
 
+void test_paste ()
+{
+    // Create the source image, make it a gradient
+    ImageSpec Aspec (4, 4, 3, TypeDesc::FLOAT);
+    ImageBuf A ("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 ("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]);
+}
+
+
+
 // Tests ImageBufAlgo::add
 void test_add ()
 {
@@ -231,6 +274,7 @@ main (int argc, char **argv)
 {
     test_zero_fill ();
     test_crop ();
+    test_paste ();
     test_add ();
     test_compare ();
     
diff --git a/src/libOpenImageIO/maketexture.cpp b/src/libOpenImageIO/maketexture.cpp
index 503bada..af92848 100644
--- a/src/libOpenImageIO/maketexture.cpp
+++ b/src/libOpenImageIO/maketexture.cpp
@@ -310,8 +310,6 @@ fix_latl_edges (ImageBuf &buf)
                 left[c] += right[c];
         }
         for (int c = 0;  c < n;  ++c)
-            left[c] += right[c];
-        for (int c = 0;  c < n;  ++c)
             left[c] *= wscale;
         for (int x = buf.xbegin();  x < buf.xend();  ++x)
             buf.setpixel (x, y, left);
diff --git a/src/libtexture/imagecache.cpp b/src/libtexture/imagecache.cpp
index ce5ea47..a919401 100644
--- a/src/libtexture/imagecache.cpp
+++ b/src/libtexture/imagecache.cpp
@@ -2419,6 +2419,8 @@ ImageCacheImpl::invalidate (ustring filename)
         tilemutex_holder (NULL);
     }
 
+    ustring fingerprint = file->fingerprint();
+
     {
         ic_write_lock fileguard (m_filemutex);
         file->invalidate ();
@@ -2427,7 +2429,7 @@ ImageCacheImpl::invalidate (ustring filename)
     // Remove the fingerprint corresponding to this file
     {
         spin_lock lock (m_fingerprints_mutex);
-        FilenameMap::iterator f = m_fingerprints.find (filename);
+        FilenameMap::iterator f = m_fingerprints.find (fingerprint);
         if (f != m_fingerprints.end())
             m_fingerprints.erase (f);
     }
diff --git a/src/libtexture/texturesys.cpp b/src/libtexture/texturesys.cpp
index c5fdd90..34b7b1a 100644
--- a/src/libtexture/texturesys.cpp
+++ b/src/libtexture/texturesys.cpp
@@ -161,16 +161,17 @@ bool
 TextureSystemImpl::wrap_periodic_sharedborder (int &coord, int origin, int width)
 {
     // Like periodic, but knowing that the first column and last are
-    // actually the same position, so we essentially skip the first
-    // column in the next cycle.  We only need this to work for one wrap
-    // in each direction since it's only used for latlong maps.
-    coord -= origin;
-    if (coord >= width) {
-        coord = coord - width + 1;
-    } else if (coord < 0) {
-        coord = coord + width - 1;
+    // actually the same position, so we essentially skip the last
+    // column in the next cycle.
+    if (width <= 2) {
+        coord = origin;  // special case -- just 1 pixel wide
+    } else {
+        coord -= origin;
+        coord %= (width-1);
+        if (coord < 0)       // Fix negative values
+            coord += width;
+        coord += origin;
     }
-    coord += origin;
     return true;
 }
 
diff --git a/src/maketx/maketx.cpp b/src/maketx/maketx.cpp
index 410cb7c..6f0fd52 100644
--- a/src/maketx/maketx.cpp
+++ b/src/maketx/maketx.cpp
@@ -624,8 +624,6 @@ fix_latl_edges (ImageBuf &buf)
                 left[c] += right[c];
         }
         for (int c = 0;  c < n;  ++c)
-            left[c] += right[c];
-        for (int c = 0;  c < n;  ++c)
             left[c] *= wscale;
         for (int x = buf.xbegin();  x < buf.xend();  ++x)
             buf.setpixel (x, y, left);
diff --git a/src/oiiotool/imagerec.cpp b/src/oiiotool/imagerec.cpp
index 65a7479..dbb7c75 100644
--- a/src/oiiotool/imagerec.cpp
+++ b/src/oiiotool/imagerec.cpp
@@ -86,18 +86,8 @@ ImageRec::ImageRec (ImageRec &img, int subimage_to_copy,
             if (writable || img.pixels_modified() || !copy_pixels) {
                 // Make our own copy of the pixels
                 ib = new ImageBuf (img.name(), srcspec);
-                if (copy_pixels) {
-                    ImageBuf::ConstIterator<float> src (srcib);
-                    ASSERT (src.rawptr());
-                    ImageBuf::Iterator<float> dst (*ib);
-                    int nchans = ib->nchannels();
-                    while (! src.done()) {
-                        for (int c = 0;  c < nchans;  ++c)
-                            dst[c] = src[c];
-                        ++src;
-                        ++dst;
-                    }
-                }
+                if (copy_pixels)
+                    ib->copy_pixels (srcib);
             } else {
                 // The other image is not modified, and we don't need to be
                 // writable, either.
diff --git a/src/oiiotool/oiiotool.cpp b/src/oiiotool/oiiotool.cpp
index 424d52b..9a70423 100644
--- a/src/oiiotool/oiiotool.cpp
+++ b/src/oiiotool/oiiotool.cpp
@@ -64,6 +64,22 @@ static Oiiotool ot;
 
 
 
+std::string
+format_resolution (int w, int h, int x, int y)
+{
+#if 0
+    // This should work...
+    return Strutil::format ("%dx%d%+d%+d", w, h, x, y);
+    // ... but tinyformat doesn't print the sign for '0' values!  It
+    // appears to be a bug with iostream use of 'showpos' format flag,
+    // specific to certain gcc libs, perhaps only on OSX.  Workaround:
+#else
+    return Strutil::format ("%dx%d%c%d%c%d", w, h,
+                            x >= 0 ? '+' : '-', abs(x),
+                            y >= 0 ? '+' : '-', abs(y));
+#endif
+}
+
 
 // FIXME -- lots of things we skimped on so far:
 // FIXME: check binary ops for compatible image dimensions
@@ -880,11 +896,18 @@ action_unmip (int argc, const char *argv[])
 // For a given spec (which contains the channel names for an image), and
 // a comma separated list of channels (e.g., "B,G,R,A"), compute the
 // vector of integer indices for those channels (e.g., {2,1,0,3}).
+// A channel may be a literal assignment (e.g., "=0.5"), or a literal
+// assignment with channel naming (e.g., "Z=0.5").
 // Return true for success, false for failure, including if any of the
-// channels were not present in the image.
+// channels were not present in the image.  Upon return, channels
+// will be the indices of the source image channels to copy (-1 for
+// channels that are not filled with source data), values will hold
+// the value to fill un-sourced channels (defaulting to zero), and
+// newchannelnames will be the name of renamed or non-default-named
+// channels (defaulting to "" if no special name is needed).
 static bool
 decode_channel_set (const ImageSpec &spec, std::string chanlist,
-                    std::vector<std::string> &channelnames,
+                    std::vector<std::string> &newchannelnames,
                     std::vector<int> &channels, std::vector<float> &values)
 {
     channels.clear ();
@@ -899,18 +922,26 @@ decode_channel_set (const ImageSpec &spec, std::string chanlist,
             chanlist = chanlist.substr (pos+1, std::string::npos);
 
         // Find the index corresponding to that channel
+        newchannelnames.push_back (std::string());
         float value = 0.0f;
         int ch = -1;
         for (int i = 0;  i < spec.nchannels;  ++i)
-            if (spec.channelnames[i] == onechan) {
-                ch = i;  break;
+            if (spec.channelnames[i] == onechan) { // name of a known channel?
+                ch = i;
+                break;
             }
         if (ch < 0 && onechan.length() &&
                 (isdigit(onechan[0]) || onechan[0] == '-'))
-            ch = atoi (onechan.c_str());
-        if (ch < 0 && onechan.length() && onechan[0] == '=')
-            value = atof (onechan.c_str()+1);
-        channelnames.push_back (onechan);
+            ch = atoi (onechan.c_str());  // numeric channel index
+        if (ch < 0 && onechan.length()) {
+            // Look for Either =val or name=val
+            size_t equal_pos = onechan.find ('=');
+            if (equal_pos != std::string::npos) {
+                value = atof (onechan.c_str()+equal_pos+1);
+                onechan.erase (equal_pos);
+                newchannelnames.back() = onechan;
+            }
+        }
         channels.push_back (ch);
         values.push_back (value);
     }
@@ -940,11 +971,11 @@ action_channels (int argc, const char *argv[])
     std::vector<ImageSpec> allspecs;
     for (int s = 0, subimages = ot.allsubimages ? A->subimages() : 1;
          s < subimages;  ++s) {
-        std::vector<std::string> channelnames;
+        std::vector<std::string> newchannelnames;
         std::vector<int> channels;
         std::vector<float> values;
         bool ok = decode_channel_set (*A->spec(s,0), chanlist,
-                                      channelnames, channels, values);
+                                      newchannelnames, channels, values);
         if (! ok) {
             ot.error (argv[0], Strutil::format("Invalid or unknown channel selection \"%s\"", chanlist));
             ot.push (A);
@@ -954,9 +985,9 @@ action_channels (int argc, const char *argv[])
         allmiplevels.push_back (miplevels);
         for (int m = 0;  m < miplevels;  ++m) {
             ImageSpec spec = *A->spec(s,m);
-            spec.nchannels = (int)channelnames.size();
-            spec.default_channel_names ();
+            spec.nchannels = (int)newchannelnames.size();
             spec.channelformats.clear();
+            spec.default_channel_names ();
             allspecs.push_back (spec);
         }
     }
@@ -969,23 +1000,16 @@ action_channels (int argc, const char *argv[])
     // Subimage by subimage, MIP level by MIP level, copy/shuffle the
     // channels individually from the source image into the result.
     for (int s = 0, subimages = R->subimages();  s < subimages;  ++s) {
-        std::vector<std::string> channelnames;
+        std::vector<std::string> newchannelnames;
         std::vector<int> channels;
         std::vector<float> values;
-        decode_channel_set (*A->spec(s,0), chanlist, channelnames,
+        decode_channel_set (*A->spec(s,0), chanlist, newchannelnames,
                             channels, values);
         for (int m = 0, miplevels = R->miplevels(s);  m < miplevels;  ++m) {
             // Shuffle the indexed/named channels
             ImageBufAlgo::channels ((*R)(s,m), (*A)(s,m), (int)channels.size(),
-                                    &channels[0], false);
-            // Set channels that are literals
-            for (int c = 0;  c < (int)channels.size();  ++c)
-                if (channels[c] < 0) {
-                    ROI roi = get_roi (*R->spec(s,m));
-                    roi.chbegin = c;
-                    roi.chend = c+1;
-                    ImageBufAlgo::fill ((*R)(s,m), &values[c], roi);
-                }
+                                    &channels[0], &values[0], &newchannelnames[0],
+                                    false);
             // Tricky subtlety: IBA::channels changed the underlying IB,
             // we may need to update the IRR's copy of the spec.
             R->update_spec_from_imagebuf(s,m);
@@ -1165,6 +1189,48 @@ action_abs (int argc, const char *argv[])
 
 
 static int
+action_cmul (int argc, const char *argv[])
+{
+    if (ot.postpone_callback (1, action_abs, argc, argv))
+        return 0;
+
+    std::vector<std::string> scalestrings;
+    Strutil::split (std::string(argv[1]), scalestrings, ",");
+    if (scalestrings.size() < 1)
+        return 0;   // Implicit multiplication by 1 if we can't figure it out
+
+    ImageRecRef A = ot.pop();
+    A->read ();
+    ImageRecRef R (new ImageRec (*A, ot.allsubimages ? -1 : 0,
+                                 ot.allsubimages ? -1 : 0,
+                                 true /*writable*/, true /*copy_pixels*/));
+    ot.push (R);
+
+    std::vector<float> scale;
+    int subimages = ot.curimg->subimages();
+    for (int s = 0;  s < subimages;  ++s) {
+        int nchans = R->spec(s,0)->nchannels;
+        scale.clear ();
+        scale.resize (nchans, atof(scalestrings[0].c_str()));
+        if (scalestrings.size() > 1) {
+            for (int c = 0;  c < nchans;  ++c) {
+                if (c < (int)scalestrings.size())
+                    scale[c] = atof(scalestrings[c].c_str());
+                else
+                    scale[c] = 1.0f;
+            }
+        }    
+        int miplevels = ot.curimg->miplevels(s);
+        for (int m = 0;  m < miplevels;  ++m)
+            ImageBufAlgo::mul ((*R)(s,m), &scale[0]);
+    }
+
+    return 0;
+}
+
+
+
+static int
 action_flip (int argc, const char *argv[])
 {
     if (ot.postpone_callback (1, action_flip, argc, argv))
@@ -1291,6 +1357,21 @@ action_dup (int argc, const char *argv[])
 }
 
 
+static int
+action_swap (int argc, const char *argv[])
+{
+    ASSERT (argc == 1);
+    if (ot.image_stack.size() < 1) {
+        ot.error (argv[0], "requires at least two loaded images");
+        return 0;
+    }
+    ImageRecRef B (ot.pop());
+    ImageRecRef A (ot.pop());
+    ot.push (B);
+    ot.push (A);
+    return 0;
+}
+
 
 static int
 action_create (int argc, const char *argv[])
@@ -1301,7 +1382,7 @@ action_create (int argc, const char *argv[])
         std::cout << "Invalid number of channels: " << nchans << "\n";
         nchans = 3;
     }
-    ImageSpec spec (64, 64, nchans);
+    ImageSpec spec (64, 64, nchans, TypeDesc::FLOAT);
     adjust_geometry (spec.width, spec.height, spec.x, spec.y, argv[1]);
     spec.full_x = spec.x;
     spec.full_y = spec.y;
@@ -1467,21 +1548,8 @@ action_croptofull (int argc, const char *argv[])
     const ImageSpec &Aspec (*A->spec(0,0));
     // Implement by calling action_crop with a geometry specifier built
     // from the current full image size.
-#if 0
-    // This should work...
-    std::string size = Strutil::format ("%dx%d%+d%+d",
-                                        Aspec.full_width, Aspec.full_height,
-                                        Aspec.full_x, Aspec.full_y);
-    // ... but tinyformat doesn't print the sign for '0' values!  It
-    // appears to be a bug with iostream use of 'showpos' format flag,
-    // specific to certain gcc libs, perhaps only on OSX.  Workaround:
-#else
-    std::string size = Strutil::format ("%dx%d%c%d%c%d",
-                                        Aspec.full_width, Aspec.full_height,
-                                        Aspec.full_x >= 0 ? '+' : '-',
-                                        Aspec.full_y >= 0 ? '+' : '-',
-                                        abs(Aspec.full_x), abs(Aspec.full_y));
-#endif
+    std::string size = format_resolution (Aspec.full_width, Aspec.full_height,
+                                          Aspec.full_x, Aspec.full_y);
     const char *newargv[2] = { "crop", size.c_str() };
     return action_crop (2, newargv);
 }
@@ -1566,6 +1634,85 @@ action_resize (int argc, const char *argv[])
 
 
 
+static int
+action_fit (int argc, const char *argv[])
+{
+    if (ot.postpone_callback (1, action_fit, argc, argv))
+        return 0;
+
+    // Examine the top of stack
+    ImageRecRef A = ot.top();
+    ot.read ();
+    const ImageSpec *Aspec = A->spec(0,0);
+
+    // Parse the user request for resolution to fit
+    int fit_full_width = Aspec->full_width;
+    int fit_full_height = Aspec->full_height;
+    int fit_full_x = Aspec->full_x;
+    int fit_full_y = Aspec->full_y;
+    adjust_geometry (fit_full_width, fit_full_height, fit_full_x, fit_full_y,
+                     argv[1], false);
+
+    // Compute scaling factors and use action_resize to do the heavy lifting
+    float wfactor = float(fit_full_width) / Aspec->full_width;
+    float hfactor = float(fit_full_height) / Aspec->full_height;
+    int resize_full_width = fit_full_width;
+    int resize_full_height = fit_full_height;
+    if (wfactor <= hfactor)
+        resize_full_height = int(Aspec->full_height * wfactor);
+    else
+        resize_full_width = int(Aspec->full_width * hfactor);
+    if (ot.verbose) {
+        std::cout << "Fitting " 
+                  << format_resolution(Aspec->full_width, Aspec->full_height,
+                                       Aspec->x, Aspec->y)
+                  << " into "
+                  << format_resolution(fit_full_width, fit_full_height,
+                                       fit_full_x, fit_full_y) 
+                  << "\n";
+        std::cout << "  Resizing to "
+                  << format_resolution(resize_full_width, resize_full_height,
+                                       fit_full_x, fit_full_y) << "\n";
+    }
+    if (resize_full_width != Aspec->full_width ||
+        resize_full_height != Aspec->full_height ||
+        fit_full_x != Aspec->full_x || fit_full_y != Aspec->full_y) {
+        std::string resize = format_resolution (resize_full_width,
+                                                resize_full_height,
+                                                fit_full_x, fit_full_y);
+        const char *newargv[2] = { "resize", resize.c_str() };
+        action_resize (2, newargv);
+        A = ot.top ();
+        Aspec = A->spec(0,0);
+        // Now A,Aspec are for the NEW resized top of stack
+    }
+
+    if (fit_full_width != resize_full_width ||
+        fit_full_height != Aspec->full_height) {
+        // Needs padding
+        ImageSpec newspec = *Aspec;
+        newspec.width = newspec.full_width = fit_full_width;
+        newspec.height = newspec.full_height = fit_full_height;
+        newspec.x = newspec.full_x = fit_full_x;
+        newspec.y = newspec.full_y = fit_full_y;
+        newspec.set_format (TypeDesc::FLOAT);
+        ImageRecRef B (new ImageRec (A->name(), newspec, ot.imagecache));
+        ImageBuf &Rib ((*B)(0,0));
+        ot.curimg = B;
+        ImageBufAlgo::zero (Rib);
+        if (Aspec->full_width == fit_full_width)
+            ImageBufAlgo::paste (Rib, 0, (fit_full_height-Aspec->full_height)/2,
+                                 0, 0, (*A)(0,0));
+        else
+            ImageBufAlgo::paste (Rib, (fit_full_width-Aspec->full_height)/2, 0,
+                                 0, 0, (*A)(0,0));
+    }
+    
+    return 0;
+}
+
+
+
 int
 action_fixnan (int argc, const char *argv[])
 {
@@ -1680,12 +1827,9 @@ action_fill (int argc, const char *argv[])
     // Read and copy the top-of-stack image
     ImageRecRef A (ot.pop());
     ot.read (A);
-    ot.push (new ImageRec (*A, 0, 0, true, false));
+    ot.push (new ImageRec (*A, 0, 0, true, true /*copy_pixels*/));
     ImageBuf &Rib ((*ot.curimg)(0,0));
     const ImageSpec &Rspec = Rib.spec();
-    bool ok = ImageBufAlgo::zero (Rib);
-    if (! ok)
-        ot.error (argv[0], Rib.geterror());
 
     int w = Rib.spec().width, h = Rib.spec().height;
     int x = Rib.spec().x, y = Rib.spec().y;
@@ -1715,7 +1859,7 @@ action_fill (int argc, const char *argv[])
         }
     }
 
-    ok = ImageBufAlgo::fill (Rib, color, ROI(x, x+w, y, y+h));
+    bool ok = ImageBufAlgo::fill (Rib, color, ROI(x, x+w, y, y+h));
     if (! ok)
         ot.error (argv[0], Rib.geterror());
 
@@ -1733,12 +1877,9 @@ action_text (int argc, const char *argv[])
     // Read and copy the top-of-stack image
     ImageRecRef A (ot.pop());
     ot.read (A);
-    ot.push (new ImageRec (*A, 0, 0, true, false));
+    ot.push (new ImageRec (*A, 0, 0, true, true /*copy_pixels*/));
     ImageBuf &Rib ((*ot.curimg)(0,0));
     const ImageSpec &Rspec = Rib.spec();
-    bool ok = ImageBufAlgo::zero (Rib);
-    if (! ok)
-        ot.error (argv[0], Rib.geterror());
 
     // Set up defaults for text placement, size, font, color
     int x = Rspec.x + Rspec.width/2;
@@ -1781,8 +1922,8 @@ action_text (int argc, const char *argv[])
         }
     }
 
-    ok = ImageBufAlgo::render_text (Rib, x, y, argv[1] /* the text */,
-                                    fontsize, font, textcolor);
+    bool ok = ImageBufAlgo::render_text (Rib, x, y, argv[1] /* the text */,
+                                         fontsize, font, textcolor);
     if (! ok)
         ot.error (argv[0], Rib.geterror());
 
@@ -1943,26 +2084,28 @@ getargs (int argc, char *argv[])
                 "--hardwarn %g", &ot.diff_hardwarn, "Warn if any one pixel difference exceeds this error (infinity)",
                 "<SEPARATOR>", "Actions:",
                 "--create %@ %s %d", action_create, NULL, NULL,
-                        "Create a blank image (optional args: geom, channels)",
+                        "Create a blank image (args: geom, channels)",
                 "--pattern %@ %s %s %d", action_pattern, NULL, NULL, NULL,
-                        "Create a patterned image (optional args: pattern, geom, channels)",
+                        "Create a patterned image (args: pattern, geom, channels)",
                 "--capture %@", action_capture, NULL,
-                        "Capture an image (optional args: camera=%d)",
+                        "Capture an image (options: camera=%d)",
                 "--diff %@", action_diff, NULL, "Print report on the difference of two images (modified by --fail, --failpercent, --hardfail, --warn, --warnpercent --hardwarn)",
                 "--add %@", action_add, NULL, "Add two images",
                 "--sub %@", action_sub, NULL, "Subtract two images",
                 "--abs %@", action_abs, NULL, "Take the absolute value of the image pixels",
+                "--cmul %s %@", action_cmul, NULL, "Multiply the image values by a constant or per-channel constants (e.g.: 0.5 or 1,1.25,0.5)",
                 "--over %@", action_over, NULL, "'Over' composite of two images",
-                "--zover %@", action_zover, NULL, "Depth composite two images with Z channels (optional args: zeroisinf=%d)",
-                "--histogram %@ %s %d", action_histogram, NULL, NULL, "Histogram one channel (optional args: cumulative=0)",
+                "--zover %@", action_zover, NULL, "Depth composite two images with Z channels (options: zeroisinf=%d)",
+                "--histogram %@ %s %d", action_histogram, NULL, NULL, "Histogram one channel (options: cumulative=0)",
                 "--flip %@", action_flip, NULL, "Flip the image vertically (top<->bottom)",
                 "--flop %@", action_flop, NULL, "Flop the image horizontally (left<->right)",
                 "--flipflop %@", action_flipflop, NULL, "Flip and flop the image (180 degree rotation)",
                 "--crop %@ %s", action_crop, NULL, "Set pixel data resolution and offset, cropping or padding if necessary (WxH+X+Y or xmin,ymin,xmax,ymax)",
                 "--croptofull %@", action_croptofull, NULL, "Crop or pad to make pixel data region match the \"full\" region",
-                "--resize %@ %s", action_resize, NULL, "Resize (640x480, 50%)",
+                "--resize %@ %s", action_resize, NULL, "Resize (640x480, 50%) (optional args: filter=%s)",
+                "--fit %@ %s", action_fit, NULL, "Resize to fit within a window size (optional args: filter=%s)",
                 "--fixnan %@ %s", action_fixnan, NULL, "Fix NaN/Inf values in the image (options: none, black, box3)",
-                "--fill %@ %s", action_fill, NULL, "Fill a region (options: x=, y=, size=, color=)",
+                "--fill %@ %s", action_fill, NULL, "Fill a region (options: color=)",
                 "--text %@ %s", action_text, NULL,
                     "Render text into the current image (options: x=, y=, size=, color=)",
                 "<SEPARATOR>", "Image stack manipulation:",
@@ -1976,6 +2119,8 @@ getargs (int argc, char *argv[])
                     "Throw away the current image",
                 "--dup %@", action_dup, NULL,
                     "Duplicate the current image (push a copy onto the stack)",
+                "--swap %@", action_swap, NULL,
+                    "Swap the top two images on the stack.",
                 "<SEPARATOR>", "Color management:",
                 "--iscolorspace %@ %s", set_colorspace, NULL,
                     "Set the assumed color space (without altering pixels)",
diff --git a/src/oiiotool/oiiotool.h b/src/oiiotool/oiiotool.h
index 584f4ae..e844ac3 100644
--- a/src/oiiotool/oiiotool.h
+++ b/src/oiiotool/oiiotool.h
@@ -146,6 +146,8 @@ public:
         return r;
     }
 
+    ImageRecRef top () { return curimg; }
+
     void error (const std::string &command, const std::string &explanation);
 
 private:
diff --git a/testsuite/maketx/ref/out.txt b/testsuite/maketx/ref/out.txt
index 315b616..d886a7f 100644
--- a/testsuite/maketx/ref/out.txt
+++ b/testsuite/maketx/ref/out.txt
@@ -349,3 +349,37 @@ small.tx             :   64 x   64, 3 channel, uint8 tiff
     compression: "zip"
     IPTC:Caption: "foo bar SHA-1=FFB354CFB97E56225E1FDA9484B8DE1B04470DAE ConstantColor=[1,0,0]"
     tiff:RowsPerStrip: "32"
+whiteenv.exr         :    4 x    2, 3 channel, half openexr (+mipmap)
+    MIP 0 of 3 (4 x 2):
+      Stats Min: 1.000000 1.000000 1.000000 (float)
+      Stats Max: 1.000000 1.000000 1.000000 (float)
+      Stats Avg: 1.000000 1.000000 1.000000 (float)
+      Stats StdDev: 0.000000 0.000000 0.000000 (float)
+      Stats NanCount: 0 0 0 
+      Stats InfCount: 0 0 0 
+      Stats FiniteCount: 8 8 8 
+      Constant: Yes
+      Constant Color: 1.000000 1.000000 1.000000 (float)
+      Monochrome: Yes
+    MIP 1 of 3 (2 x 1):
+      Stats Min: 1.000000 1.000000 1.000000 (float)
+      Stats Max: 1.000000 1.000000 1.000000 (float)
+      Stats Avg: 1.000000 1.000000 1.000000 (float)
+      Stats StdDev: 0.000000 0.000000 0.000000 (float)
+      Stats NanCount: 0 0 0 
+      Stats InfCount: 0 0 0 
+      Stats FiniteCount: 2 2 2 
+      Constant: Yes
+      Constant Color: 1.000000 1.000000 1.000000 (float)
+      Monochrome: Yes
+    MIP 2 of 3 (1 x 1):
+      Stats Min: 1.000000 1.000000 1.000000 (float)
+      Stats Max: 1.000000 1.000000 1.000000 (float)
+      Stats Avg: 1.000000 1.000000 1.000000 (float)
+      Stats StdDev: 0.000000 0.000000 0.000000 (float)
+      Stats NanCount: 0 0 0 
+      Stats InfCount: 0 0 0 
+      Stats FiniteCount: 1 1 1 
+      Constant: Yes
+      Constant Color: 1.000000 1.000000 1.000000 (float)
+      Monochrome: Yes
diff --git a/testsuite/maketx/run.py b/testsuite/maketx/run.py
index e0f04ef..a79b6ba 100644
--- a/testsuite/maketx/run.py
+++ b/testsuite/maketx/run.py
@@ -90,6 +90,16 @@ command += info_command ("small.tif", safematch=1);
 command += maketx_command ("small.tif", "small.tx",
                            "--oiio --constant-color-detect", showinfo=True)
 
+# Regression test -- at one point, we had a bug where we were botching
+# the poles of OpenEXR env maps, adding energy.  Check it by creating an
+# all-white image, turning it into an env map, and calculating its
+# statistics (should be 1.0 everywhere).
+command += (oiio_app("oiiotool") + " --pattern constant:color=1,1,1 4x2 3 "
+            + " -d half -o " + oiio_relpath("white.exr") + " >> out.txt;\n")
+command += maketx_command ("white.exr", "whiteenv.exr",
+                           "--envlatl")
+command += (oiio_app("oiiotool") + "--stats whiteenv.exr"
+            + " >> out.txt;\n")
 
 outputs = [ "out.txt" ]
 
diff --git a/testsuite/oiiotool/ref/cmul1.exr b/testsuite/oiiotool/ref/cmul1.exr
new file mode 100644
index 0000000..8e3b039
Binary files /dev/null and b/testsuite/oiiotool/ref/cmul1.exr differ
diff --git a/testsuite/oiiotool/ref/cmul2.exr b/testsuite/oiiotool/ref/cmul2.exr
new file mode 100644
index 0000000..2248d57
Binary files /dev/null and b/testsuite/oiiotool/ref/cmul2.exr differ
diff --git a/testsuite/oiiotool/ref/filled.tif b/testsuite/oiiotool/ref/filled.tif
new file mode 100644
index 0000000..0f41983
Binary files /dev/null and b/testsuite/oiiotool/ref/filled.tif differ
diff --git a/testsuite/oiiotool/ref/fit.tif b/testsuite/oiiotool/ref/fit.tif
new file mode 100644
index 0000000..2dbf304
Binary files /dev/null and b/testsuite/oiiotool/ref/fit.tif differ
diff --git a/testsuite/oiiotool/ref/out.txt b/testsuite/oiiotool/ref/out.txt
index 6d8f11a..26b80d1 100644
--- a/testsuite/oiiotool/ref/out.txt
+++ b/testsuite/oiiotool/ref/out.txt
@@ -20,13 +20,21 @@ constant.tif         :  320 x  240, 4 channel, float tiff
     Constant: Yes
     Constant Color: 0.100000 0.200000 0.300000 1.000000 (float)
     Monochrome: No
+Comparing "filled.tif" and "ref/filled.tif"
+PASS
 Comparing "resize.tif" and "ref/resize.tif"
 PASS
 Comparing "resize2.tif" and "ref/resize2.tif"
 PASS
+Comparing "fit.tif" and "ref/fit.tif"
+PASS
 Comparing "histogram_regular.tif" and "ref/histogram_regular.tif"
 PASS
 Comparing "histogram_cumulative.tif" and "ref/histogram_cumulative.tif"
 PASS
 Comparing "chanshuffle.tif" and "ref/chanshuffle.tif"
 PASS
+Comparing "cmul1.exr" and "ref/cmul1.exr"
+PASS
+Comparing "cmul2.exr" and "ref/cmul2.exr"
+PASS
diff --git a/testsuite/oiiotool/run.py b/testsuite/oiiotool/run.py
index 713ad9f..eed1ab2 100755
--- a/testsuite/oiiotool/run.py
+++ b/testsuite/oiiotool/run.py
@@ -2,7 +2,7 @@
 
 # test --create
 command += (oiio_app("oiiotool") 
-            + " --create 320x240 3 -o black.tif >> out.txt ;\n")
+            + " --create 320x240 3 -d uint8 -o black.tif >> out.txt ;\n")
 command += oiio_app("oiiotool") + " --stats black.tif >> out.txt ;\n"
 
 # test --pattern constant
@@ -11,6 +11,11 @@ command += (oiio_app("oiiotool")
             + " -o constant.tif >> out.txt ;\n")
 command += oiio_app("oiiotool") + " --stats constant.tif >> out.txt ;\n"
 
+# test --fill
+command += (oiio_app("oiiotool")
+            + " --create 256x256 3 --fill:color=1,.5,.5 256x256"
+            + " --fill:color=0,1,0 80x80+100+100 -d uint8 -o filled.tif >> out.txt ;\n")
+
 # test resize
 command += (oiio_app ("oiiotool") + " " 
             + parent + "/oiio-images/grid.tif"
@@ -19,6 +24,21 @@ command += (oiio_app ("oiiotool") + " "
             + parent + "/oiio-images/grid.tif"
             + " --resize 25% -o resize2.tif >> out.txt ;\n")
 
+# test fit
+command += (oiio_app ("oiiotool") + " " 
+            + parent + "/oiio-images/grid.tif"
+            + " --fit 360x240 -d uint8 -o fit.tif >> out.txt ;\n")
+
+# test --cmul
+# First, make a small gray swatch
+command += (oiio_app ("oiiotool") + " --pattern constant:color=0.5,0.5,0.5 128x128 3 -d half -o cmul-input.exr >> out.txt ;\n")
+# Test --cmul val (multiply all channels by the same scalar)
+command += (oiio_app ("oiiotool")
+            + " cmul-input.exr --cmul 1.5 -o cmul1.exr >> out.txt ;\n")
+# Test --cmul val,val,val... (multiply per-channel scalars)
+command += (oiio_app ("oiiotool")
+            + " cmul-input.exr --cmul 1.5,1,0.5 -o cmul2.exr >> out.txt ;\n")
+
 # test histogram generation
 command += (oiio_app ("oiiotool") + " "
             + "ref/histogram_input.png"
@@ -40,9 +60,9 @@ command += (oiio_app ("oiiotool") + " "
 
 
 # Outputs to check against references
-outputs = [ "resize.tif", "resize2.tif",
+outputs = [ "filled.tif", "resize.tif", "resize2.tif", "fit.tif",
             "histogram_regular.tif", "histogram_cumulative.tif",
-            "chanshuffle.tif",
+            "chanshuffle.tif", "cmul1.exr", "cmul2.exr",
             "out.txt" ]
 
 #print "Running this command:\n" + command + "\n"

-- 
OpenImageIO packaging



More information about the Pkg-phototools-commits mailing list