1 // Copyright 2008-present Contributors to the OpenImageIO project.
2 // SPDX-License-Identifier: BSD-3-Clause
3 // https://github.com/OpenImageIO/oiio/blob/master/LICENSE.md
4 
5 /// \file
6 /// Implementation of ImageBufAlgo algorithms that merely move pixels
7 /// or channels between images without altering their values.
8 
9 
10 #include <cmath>
11 #include <iostream>
12 
13 #include <OpenImageIO/deepdata.h>
14 #include <OpenImageIO/imagebuf.h>
15 #include <OpenImageIO/imagebufalgo.h>
16 #include <OpenImageIO/imagebufalgo_util.h>
17 #include <OpenImageIO/thread.h>
18 
19 #include "imageio_pvt.h"
20 
21 
22 OIIO_NAMESPACE_BEGIN
23 
24 
25 template<typename DSTTYPE>
26 static bool
channels_(ImageBuf & dst,const ImageBuf & src,cspan<int> channelorder,cspan<float> channelvalues,ROI roi,int nthreads=0)27 channels_(ImageBuf& dst, const ImageBuf& src, cspan<int> channelorder,
28           cspan<float> channelvalues, ROI roi, int nthreads = 0)
29 {
30     ImageBufAlgo::parallel_image(roi, nthreads, [&](ROI roi) {
31         int nchannels = src.nchannels();
32         ImageBuf::ConstIterator<DSTTYPE> s(src, roi);
33         ImageBuf::Iterator<DSTTYPE> d(dst, roi);
34         for (; !s.done(); ++s, ++d) {
35             for (int c = roi.chbegin; c < roi.chend; ++c) {
36                 int cc = channelorder[c];
37                 if (cc >= 0 && cc < nchannels)
38                     d[c] = s[cc];
39                 else if (channelvalues.size() > c)
40                     d[c] = channelvalues[c];
41             }
42         }
43     });
44     return true;
45 }
46 
47 
48 
49 bool
channels(ImageBuf & dst,const ImageBuf & src,int nchannels,cspan<int> channelorder,cspan<float> channelvalues,cspan<std::string> newchannelnames,bool shuffle_channel_names,int nthreads)50 ImageBufAlgo::channels(ImageBuf& dst, const ImageBuf& src, int nchannels,
51                        cspan<int> channelorder, cspan<float> channelvalues,
52                        cspan<std::string> newchannelnames,
53                        bool shuffle_channel_names, int nthreads)
54 {
55     // Handle in-place case
56     if (&dst == &src) {
57         ImageBuf tmp = src;
58         return channels(dst, tmp, nchannels, channelorder, channelvalues,
59                         newchannelnames, shuffle_channel_names, nthreads);
60     }
61 
62     pvt::LoggedTimer logtime("IBA::channels");
63     // Not intended to create 0-channel images.
64     if (nchannels <= 0) {
65         dst.errorf("%d-channel images not supported", nchannels);
66         return false;
67     }
68     // If we dont have a single source channel,
69     // hard to know how big to make the additional channels
70     if (src.spec().nchannels == 0) {
71         dst.errorf("%d-channel images not supported", src.spec().nchannels);
72         return false;
73     }
74 
75     // If channelorder is NULL, it will be interpreted as
76     // {0, 1, ..., nchannels-1}.
77     int* local_channelorder = NULL;
78     if (channelorder.empty()) {
79         local_channelorder = OIIO_ALLOCA(int, nchannels);
80         for (int c = 0; c < nchannels; ++c)
81             local_channelorder[c] = c;
82         channelorder = cspan<int>(local_channelorder, nchannels);
83     }
84 
85     // If this is the identity transformation, just do a simple copy
86     bool inorder = true;
87     for (int c = 0; c < nchannels; ++c) {
88         inorder &= (channelorder[c] == c);
89         if (newchannelnames.size() > c && newchannelnames[c].size()
90             && c < int(src.spec().channelnames.size()))
91             inorder &= (newchannelnames[c] == src.spec().channelnames[c]);
92     }
93     if (nchannels == src.spec().nchannels && inorder) {
94         return dst.copy(src);
95     }
96 
97     // Construct a new ImageSpec that describes the desired channel ordering.
98     ImageSpec newspec = src.spec();
99     newspec.nchannels = nchannels;
100     newspec.default_channel_names();
101     newspec.channelformats.clear();
102     newspec.alpha_channel = -1;
103     newspec.z_channel     = -1;
104     bool all_same_type    = true;
105     for (int c = 0; c < nchannels; ++c) {
106         int csrc = channelorder[c];
107         // If the user gave an explicit name for this channel, use it...
108         if (newchannelnames.size() > c && newchannelnames[c].size())
109             newspec.channelnames[c] = newchannelnames[c];
110         // otherwise, if shuffle_channel_names, use the channel name of
111         // the src channel we're using (otherwise stick to the default name)
112         else if (shuffle_channel_names && csrc >= 0
113                  && csrc < src.spec().nchannels)
114             newspec.channelnames[c] = src.spec().channelnames[csrc];
115         // otherwise, use the name of the source in that slot
116         else if (csrc >= 0 && csrc < src.spec().nchannels) {
117             newspec.channelnames[c] = src.spec().channelnames[csrc];
118         }
119         TypeDesc type = src.spec().channelformat(csrc);
120         newspec.channelformats.push_back(type);
121         if (type != newspec.channelformats.front())
122             all_same_type = false;
123         // Use the names (or designation of the src image, if
124         // shuffle_channel_names is true) to deduce the alpha and z channels.
125         if ((shuffle_channel_names && csrc == src.spec().alpha_channel)
126             || Strutil::iequals(newspec.channelnames[c], "A")
127             || Strutil::iequals(newspec.channelnames[c], "alpha"))
128             newspec.alpha_channel = c;
129         if ((shuffle_channel_names && csrc == src.spec().z_channel)
130             || Strutil::iequals(newspec.channelnames[c], "Z"))
131             newspec.z_channel = c;
132     }
133     if (all_same_type)                   // clear per-chan formats if
134         newspec.channelformats.clear();  // they're all the same
135 
136     // Update the image (realloc with the new spec)
137     dst.reset(newspec);
138 
139     if (dst.deep()) {
140         // Deep case:
141         OIIO_DASSERT(src.deep() && src.deepdata() && dst.deepdata());
142         const DeepData& srcdata(*src.deepdata());
143         DeepData& dstdata(*dst.deepdata());
144         // The earlier dst.alloc() already called dstdata.init()
145         for (int p = 0, npels = (int)newspec.image_pixels(); p < npels; ++p)
146             dstdata.set_samples(p, srcdata.samples(p));
147         for (int p = 0, npels = (int)newspec.image_pixels(); p < npels; ++p) {
148             if (!dstdata.samples(p))
149                 continue;  // no samples for this pixel
150             for (int c = 0; c < newspec.nchannels; ++c) {
151                 int csrc = channelorder[c];
152                 if (csrc < 0) {
153                     // Replacing the channel with a new value
154                     float val = channelvalues.size() > c ? channelvalues[c]
155                                                          : 0.0f;
156                     for (int s = 0, ns = dstdata.samples(p); s < ns; ++s)
157                         dstdata.set_deep_value(p, c, s, val);
158                 } else {
159                     if (dstdata.channeltype(c) == TypeDesc::UINT)
160                         for (int s = 0, ns = dstdata.samples(p); s < ns; ++s)
161                             dstdata.set_deep_value(
162                                 p, c, s, srcdata.deep_value_uint(p, csrc, s));
163                     else
164                         for (int s = 0, ns = dstdata.samples(p); s < ns; ++s)
165                             dstdata.set_deep_value(p, c, s,
166                                                    srcdata.deep_value(p, csrc,
167                                                                       s));
168                 }
169             }
170         }
171         return true;
172     }
173     // Below is the non-deep case
174 
175     bool ok;
176     OIIO_DISPATCH_TYPES(ok, "channels", channels_, dst.spec().format, dst, src,
177                         channelorder, channelvalues, dst.roi(), nthreads);
178     return ok;
179 }
180 
181 
182 
183 ImageBuf
channels(const ImageBuf & src,int nchannels,cspan<int> channelorder,cspan<float> channelvalues,cspan<std::string> newchannelnames,bool shuffle_channel_names,int nthreads)184 ImageBufAlgo::channels(const ImageBuf& src, int nchannels,
185                        cspan<int> channelorder, cspan<float> channelvalues,
186                        cspan<std::string> newchannelnames,
187                        bool shuffle_channel_names, int nthreads)
188 {
189     ImageBuf result;
190     bool ok = channels(result, src, nchannels, channelorder, channelvalues,
191                        newchannelnames, shuffle_channel_names, nthreads);
192     if (!ok && !result.has_error())
193         result.errorf("ImageBufAlgo::channels() error");
194     return result;
195 }
196 
197 
198 
199 template<class Rtype, class Atype, class Btype>
200 static bool
channel_append_impl(ImageBuf & dst,const ImageBuf & A,const ImageBuf & B,ROI roi,int nthreads)201 channel_append_impl(ImageBuf& dst, const ImageBuf& A, const ImageBuf& B,
202                     ROI roi, int nthreads)
203 {
204     ImageBufAlgo::parallel_image(roi, nthreads, [&](ROI roi) {
205         int na = A.nchannels(), nb = B.nchannels();
206         int n = std::min(dst.nchannels(), na + nb);
207         ImageBuf::Iterator<Rtype> r(dst, roi);
208         ImageBuf::ConstIterator<Atype> a(A, roi);
209         ImageBuf::ConstIterator<Btype> b(B, roi);
210         for (; !r.done(); ++r, ++a, ++b) {
211             for (int c = 0; c < n; ++c) {
212                 if (c < na)
213                     r[c] = a.exists() ? a[c] : 0.0f;
214                 else
215                     r[c] = b.exists() ? b[c - na] : 0.0f;
216             }
217         }
218     });
219     return true;
220 }
221 
222 
223 bool
channel_append(ImageBuf & dst,const ImageBuf & A,const ImageBuf & B,ROI roi,int nthreads)224 ImageBufAlgo::channel_append(ImageBuf& dst, const ImageBuf& A,
225                              const ImageBuf& B, ROI roi, int nthreads)
226 {
227     pvt::LoggedTimer logtime("IBA::channel_append");
228     // If the region is not defined, set it to the union of the valid
229     // regions of the two source images.
230     if (!roi.defined())
231         roi = roi_union(get_roi(A.spec()), get_roi(B.spec()));
232 
233     // If dst has not already been allocated, set it to the right size,
234     // make it a type that can hold both A's and B's type.
235     if (!dst.pixels_valid()) {
236         ImageSpec dstspec = A.spec();
237         dstspec.set_format(
238             TypeDesc::basetype_merge(A.spec().format, B.spec().format));
239         // Append the channel descriptions
240         dstspec.nchannels = A.spec().nchannels + B.spec().nchannels;
241         for (int c = 0; c < B.spec().nchannels; ++c) {
242             std::string name = B.spec().channelnames[c];
243             // It's a duplicate channel name. This will wreak havoc for
244             // OpenEXR, so we need to choose a unique name.
245             if (std::find(dstspec.channelnames.begin(),
246                           dstspec.channelnames.end(), name)
247                 != dstspec.channelnames.end()) {
248                 // First, let's see if the original image had a subimage
249                 // name and use that.
250                 std::string subname = B.spec().get_string_attribute(
251                     "oiio:subimagename");
252                 if (subname.size())
253                     name = subname + "." + name;
254             }
255             if (std::find(dstspec.channelnames.begin(),
256                           dstspec.channelnames.end(), name)
257                 != dstspec.channelnames.end()) {
258                 // If it's still a duplicate, fall back on a totally
259                 // artificial name that contains the channel number.
260                 name = Strutil::sprintf("channel%d", A.spec().nchannels + c);
261             }
262             dstspec.channelnames.push_back(name);
263         }
264         if (dstspec.alpha_channel < 0 && B.spec().alpha_channel >= 0)
265             dstspec.alpha_channel = B.spec().alpha_channel + A.nchannels();
266         if (dstspec.z_channel < 0 && B.spec().z_channel >= 0)
267             dstspec.z_channel = B.spec().z_channel + A.nchannels();
268         set_roi(dstspec, roi);
269         dst.reset(dstspec);
270     }
271 
272     bool ok;
273     OIIO_DISPATCH_COMMON_TYPES3(ok, "channel_append", channel_append_impl,
274                                 dst.spec().format, A.spec().format,
275                                 B.spec().format, dst, A, B, roi, nthreads);
276     return ok;
277 }
278 
279 
280 ImageBuf
channel_append(const ImageBuf & A,const ImageBuf & B,ROI roi,int nthreads)281 ImageBufAlgo::channel_append(const ImageBuf& A, const ImageBuf& B, ROI roi,
282                              int nthreads)
283 {
284     ImageBuf result;
285     bool ok = channel_append(result, A, B, roi, nthreads);
286     if (!ok && !result.has_error())
287         result.errorf("channel_append error");
288     return result;
289 }
290 
291 
292 OIIO_NAMESPACE_END
293