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