@@ -46,11 +46,34 @@ string GainmapHeadroom::toString() const {
4646 }
4747}
4848
49+ static vector<Channel*> getRgbOrLuminanceChannels (ImageData& image) {
50+ image.updateLayers ();
51+
52+ Channel* r = nullptr ;
53+ Channel* g = nullptr ;
54+ Channel* b = nullptr ;
55+
56+ for (auto & layer : image.layers ) {
57+ if (((r = image.mutableChannel (layer + " R" )) && (g = image.mutableChannel (layer + " G" )) && (b = image.mutableChannel (layer + " B" ))) ||
58+ ((r = image.mutableChannel (layer + " r" )) && (g = image.mutableChannel (layer + " g" )) && (b = image.mutableChannel (layer + " b" )))) {
59+ return {r, g, b};
60+ } else if ((r = image.mutableChannel (layer + " L" )) || (r = image.mutableChannel (layer + " l" )) ||
61+ (r = image.mutableChannel (layer + " Y" )) || (r = image.mutableChannel (layer + " y" ))) {
62+ return {r};
63+ }
64+ }
65+
66+ return {};
67+ }
68+
4969Task<void > preprocessAndApplyAppleGainMap (
5070 ImageData& image, ImageData& gainMap, const optional<Ifd>& amn, const GainmapHeadroom& targetHeadroom, int priority
5171) {
52- if (image.channels .empty () || gainMap.channels .empty ()) {
53- tlog::warning () << " ISO gain map: image or gain map has no channels. Skipping gain map application." ;
72+ const auto imageChannels = getRgbOrLuminanceChannels (image);
73+ auto gainMapChannels = getRgbOrLuminanceChannels (gainMap);
74+
75+ if (imageChannels.empty () || gainMapChannels.empty ()) {
76+ tlog::warning () << " Apple gain map: image or gain map has no channels. Skipping gain map application." ;
5477 co_return ;
5578 }
5679
@@ -59,28 +82,33 @@ Task<void> preprocessAndApplyAppleGainMap(
5982 tlog::debug () << " Apple gain map: linearizing and resizing" ;
6083
6184 // First: linearize per the spec, then resize to image size
62- const auto gainmapSize = gainMap. channels [ 0 ]. size ();
85+ const auto gainmapSize = gainMapChannels. front ()-> size ();
6386 const size_t gainmapNumPixels = (size_t )gainmapSize.x () * gainmapSize.y ();
6487 co_await ThreadPool::global ().parallelForAsync <size_t >(
6588 0 ,
6689 gainmapNumPixels,
67- gainmapNumPixels * gainMap. channels .size (),
90+ gainmapNumPixels * gainMapChannels .size (),
6891 [&](int i) {
69- for (int c = 0 ; c < (int )gainMap. channels .size (); ++c) {
92+ for (int c = 0 ; c < (int )gainMapChannels .size (); ++c) {
7093 // NOTE: The docs (above link) say to use the Rec.709 transfer function here, but comparisons with ISO gain maps indicate
7194 // that the gain maps are actually encoded with the sRGB transfer function.
72- // const float gain = ituth273::invTransferComponent(ituth273::ETransfer::BT709, gainMap.channels [gainmapChannel].at(i));
73- gainMap. channels [c]. setAt (i, toLinear (gainMap. channels [c]. at (i)));
95+ // const float gain = ituth273::invTransferComponent(ituth273::ETransfer::BT709, gainMapChannels [gainmapChannel].at(i));
96+ gainMapChannels [c]-> setAt (i, toLinear (gainMapChannels [c]-> at (i)));
7497 }
7598 },
7699 priority
77100 );
78101
79- const auto size = image. channels [ 0 ]. size ();
102+ const auto size = imageChannels. front ()-> size ();
80103
81104 co_await ImageLoader::resizeImageData (gainMap, size, priority);
82105
83- TEV_ASSERT (size == gainMap.channels [0 ].size (), " Image and gain map must have the same size." );
106+ // Re-fetch channels after resize
107+ gainMapChannels = getRgbOrLuminanceChannels (gainMap);
108+ TEV_ASSERT (!gainMapChannels.empty (), " Gain map must have at least one channel after resize." );
109+ TEV_ASSERT (
110+ size == gainMapChannels.front ()->size (), " Image and gain map must have the same size. ({}!={})" , size, gainMapChannels.front ()->size ()
111+ );
84112
85113 // 0.0 and 8.0 result in the weakest effect. They are a sane default; see https://developer.apple.com/forums/thread/709331
86114 float maker33 = 0 .0f ;
@@ -121,45 +149,23 @@ Task<void> preprocessAndApplyAppleGainMap(
121149 " Apple gain map: derived weight {} from headroom {} and maker note #33={} #48={}" , headroom, targetHeadroom.toString (), maker33, maker48
122150 );
123151
124- const int numImageChannels = (int )image.channels .size ();
125- const int numGainMapChannels = (int )gainMap.channels .size ();
126-
127- if (numGainMapChannels > 1 ) {
152+ if (gainMapChannels.size () > 1 ) {
128153 tlog::warning () << " Apple gain map: should only have one channel. Attempting to apply multi-channel gain map." ;
129154 }
130155
131- int alphaChannelIndex = -1 ;
132- for (int c = 0 ; c < numImageChannels; ++c) {
133- bool isAlpha = Channel::isAlpha (image.channels [c].name ());
134- if (isAlpha) {
135- if (alphaChannelIndex != -1 ) {
136- tlog::warning () << fmt::format (
137- " Apple gain map: image has multiple alpha channels, using the first one: {}" , image.channels [alphaChannelIndex].name ()
138- );
139- continue ;
140- }
141-
142- alphaChannelIndex = c;
143- }
144- }
145-
146156 const size_t numPixels = (size_t )size.x () * size.y ();
147157 co_await ThreadPool::global ().parallelForAsync <size_t >(
148158 0 ,
149159 numPixels,
150- numPixels * numImageChannels ,
160+ numPixels * imageChannels. size () ,
151161 [&](size_t i) {
152- for (int c = 0 ; c < numImageChannels; ++c) {
153- if (c == alphaChannelIndex) {
154- continue ;
155- }
162+ for (size_t c = 0 ; c < imageChannels.size (); ++c) {
163+ const size_t gainmapChannel = std::min (c, gainMapChannels.size () - 1 );
156164
157- const int gainmapChannel = std::min (c, numGainMapChannels - 1 );
165+ const float sdr = imageChannels[c]->at (i);
166+ const float gain = gainMapChannels[gainmapChannel]->at (i);
158167
159- const float sdr = image.channels [c].at (i);
160- const float gain = gainMap.channels [gainmapChannel].at (i);
161-
162- image.channels [c].setAt (i, sdr * (1 .0f + (headroom - 1 .0f ) * gain));
168+ imageChannels[c]->setAt (i, sdr * (1 .0f + (headroom - 1 .0f ) * gain));
163169 }
164170 },
165171 priority
@@ -177,7 +183,10 @@ Task<void> preprocessAndApplyIsoGainMap(
177183 const GainmapHeadroom& targetHeadroom,
178184 int priority
179185) {
180- if (image.channels .empty () || gainMap.channels .empty ()) {
186+ const auto imageChannels = getRgbOrLuminanceChannels (image);
187+ auto gainMapChannels = getRgbOrLuminanceChannels (gainMap);
188+
189+ if (imageChannels.empty () || gainMapChannels.empty ()) {
181190 tlog::warning () << " ISO gain map: image or gain map has no channels. Skipping gain map application." ;
182191 co_return ;
183192 }
@@ -196,40 +205,35 @@ Task<void> preprocessAndApplyIsoGainMap(
196205 );
197206
198207 // Per the spec, unnormalize and then resize (in log space) to image size
199- const auto gainmapSize = gainMap. channels [ 0 ]. size ();
208+ const auto gainmapSize = gainMapChannels. front ()-> size ();
200209 const size_t gainmapNumPixels = (size_t )gainmapSize.x () * gainmapSize.y ();
201210 co_await ThreadPool::global ().parallelForAsync <size_t >(
202211 0 ,
203212 gainmapNumPixels,
204- gainmapNumPixels * gainMap. channels .size (),
213+ gainmapNumPixels * gainMapChannels .size (),
205214 [&](int i) {
206- for (int c = 0 ; c < (int )gainMap. channels .size (); ++c) {
207- const float val = gainMap. channels [c]. at (i);
215+ for (int c = 0 ; c < (int )gainMapChannels .size (); ++c) {
216+ const float val = gainMapChannels [c]-> at (i);
208217
209218 const float logRecovery = copysign (std::pow (abs (val), 1 .0f / metadata.gainMapGamma ()[c]), val);
210219 const float logBoost = metadata.gainMapMin ()[c] * (1 .0f - logRecovery) + metadata.gainMapMax ()[c] * logRecovery;
211220
212- gainMap. channels [c]. setAt (i, logBoost);
221+ gainMapChannels [c]-> setAt (i, logBoost);
213222 }
214223 },
215224 priority
216225 );
217226
218- const auto size = image. channels [ 0 ]. size ();
227+ const auto size = imageChannels. front ()-> size ();
219228
220229 co_await ImageLoader::resizeImageData (gainMap, size, priority);
221230
222- TEV_ASSERT (size == gainMap.channels [0 ].size (), " Image and gain map must have the same size." );
223-
224- const int numImageChannels = (int )image.channels .size ();
225- const int numGainMapChannels = (int )gainMap.channels .size ();
226-
227- int alphaChannelIndex = -1 ;
228- if (Channel::isAlpha (image.channels .back ().name ())) {
229- alphaChannelIndex = image.channels .size () - 1 ;
230- }
231-
232- const int numColorChannels = std::min (alphaChannelIndex == -1 ? numImageChannels : (numImageChannels - 1 ), 3 );
231+ // Re-fetch channels after resize
232+ gainMapChannels = getRgbOrLuminanceChannels (gainMap);
233+ TEV_ASSERT (!gainMapChannels.empty (), " Gain map must have at least one channel after resize." );
234+ TEV_ASSERT (
235+ size == gainMapChannels.front ()->size (), " Image and gain map must have the same size. ({}!={})" , size, gainMapChannels.front ()->size ()
236+ );
233237
234238 // Before applying the gainmap, convert the image to the appropriate color space. Fall back to base chroma if alt chroma requested but
235239 // not given (image should have been left in base chroma in that case).
@@ -265,17 +269,17 @@ Task<void> preprocessAndApplyIsoGainMap(
265269 co_await ThreadPool::global ().parallelForAsync <size_t >(
266270 0 ,
267271 numPixels,
268- numPixels * numColorChannels ,
272+ numPixels * imageChannels. size () ,
269273 [&](size_t i) {
270- for (int c = 0 ; c < numColorChannels ; ++c) {
271- const int gainmapChannel = std::min (c, numGainMapChannels - 1 );
274+ for (size_t c = 0 ; c < imageChannels. size () ; ++c) {
275+ const int gainmapChannel = std::min (c, gainMapChannels. size () - 1 );
272276
273- const float logBoost = gainMap. channels [gainmapChannel]. at (i);
277+ const float logBoost = gainMapChannels [gainmapChannel]-> at (i);
274278
275- const float sdr = image. channels [c]. at (i);
279+ const float sdr = imageChannels [c]-> at (i);
276280 const float hdr = (sdr + metadata.baseOffset ()[c]) * exp2f (logBoost * weight) - metadata.alternateOffset ()[c];
277281
278- image. channels [c]. setAt (i, hdr);
282+ imageChannels [c]-> setAt (i, hdr);
279283 }
280284 },
281285 priority
0 commit comments