1 /*
2 * This file is part of RawTherapee.
3 *
4 * Copyright (c) 2004-2010 Gabor Horvath <hgabor@rawtherapee.com>
5 *
6 * RawTherapee is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * RawTherapee is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with RawTherapee. If not, see <http://www.gnu.org/licenses/>.
19 */
20 #include "dcrop.h"
21 #include "curves.h"
22 #include "mytime.h"
23 #include "refreshmap.h"
24 #include "rt_math.h"
25
26 namespace {
27
28 // "ceil" rounding
29 template<typename T>
skips(T a,T b)30 constexpr T skips(T a, T b)
31 {
32 return a / b + static_cast<bool>(a % b);
33 }
34
35 } // namespace
36
37 namespace rtengine {
38
39 extern const Settings* settings;
40
Crop(ImProcCoordinator * parent,EditDataProvider * editDataProvider,bool isDetailWindow)41 Crop::Crop(ImProcCoordinator* parent, EditDataProvider *editDataProvider, bool isDetailWindow)
42 : PipetteBuffer(editDataProvider), origCrop(nullptr), spotCrop(nullptr),
43 //laboCrop(nullptr), labnCrop(nullptr),
44 cropImg (nullptr), transCrop (nullptr),
45 updating(false), newUpdatePending(false), skip(10),
46 cropx(0), cropy(0), cropw(-1), croph(-1),
47 trafx(0), trafy(0), trafw(-1), trafh(-1),
48 rqcropx(0), rqcropy(0), rqcropw(-1), rqcroph(-1),
49 borderRequested(32), upperBorder(0), leftBorder(0),
50 cropAllocated(false),
51 cropImageListener(nullptr), parent(parent), isDetailWindow(isDetailWindow)
52 {
53 for (int i = 0; i < 3; ++i) {
54 bufs_[i] = nullptr;
55 }
56 for (auto &s : pipeline_stop_) {
57 s = false;
58 }
59 parent->crops.push_back(this);
60 }
61
~Crop()62 Crop::~Crop()
63 {
64
65 MyMutex::MyLock cropLock(cropMutex);
66
67 std::vector<Crop*>::iterator i = std::find(parent->crops.begin(), parent->crops.end(), this);
68
69 if (i != parent->crops.end()) {
70 parent->crops.erase(i);
71 }
72
73 MyMutex::MyLock processingLock(parent->mProcessing);
74 freeAll();
75 }
76
destroy()77 void Crop::destroy()
78 {
79 MyMutex::MyLock lock(cropMutex);
80 MyMutex::MyLock processingLock(parent->mProcessing);
81 freeAll();
82 }
83
setListener(DetailedCropListener * il)84 void Crop::setListener(DetailedCropListener* il)
85 {
86 // We can make reads in the IF, because the mProcessing lock is only needed for change
87 if (cropImageListener != il) {
88 MyMutex::MyLock lock(cropMutex);
89 cropImageListener = il;
90 }
91 }
92
getCurrEditID()93 EditUniqueID Crop::getCurrEditID()
94 {
95 EditSubscriber *subscriber = PipetteBuffer::dataProvider ? PipetteBuffer::dataProvider->getCurrSubscriber() : nullptr;
96 return subscriber ? subscriber->getEditID() : EUID_None;
97 }
98
99 /*
100 * Delete the edit image buffer if there's no subscriber anymore.
101 * If allocation has to be done, it is deferred to Crop::update
102 */
setEditSubscriber(EditSubscriber * newSubscriber)103 void Crop::setEditSubscriber(EditSubscriber* newSubscriber)
104 {
105 MyMutex::MyLock lock(cropMutex);
106
107 // At this point, editCrop.dataProvider->currSubscriber is the old subscriber
108 EditSubscriber *oldSubscriber = PipetteBuffer::dataProvider ? PipetteBuffer::dataProvider->getCurrSubscriber() : nullptr;
109
110 if (newSubscriber == nullptr || (oldSubscriber != nullptr && oldSubscriber->getPipetteBufferType() != newSubscriber->getPipetteBufferType())) {
111 if (PipetteBuffer::imgFloatBuffer != nullptr) {
112 delete PipetteBuffer::imgFloatBuffer;
113 PipetteBuffer::imgFloatBuffer = nullptr;
114 }
115
116 if (PipetteBuffer::LabBuffer != nullptr) {
117 delete PipetteBuffer::LabBuffer;
118 PipetteBuffer::LabBuffer = nullptr;
119 }
120
121 if (PipetteBuffer::singlePlaneBuffer.getWidth() != -1) {
122 PipetteBuffer::singlePlaneBuffer.flushData();
123 }
124 }
125
126 // If oldSubscriber == NULL && newSubscriber != NULL && newSubscriber->getEditingType() == ET_PIPETTE-> the image will be allocated when necessary
127 }
128
hasListener()129 bool Crop::hasListener()
130 {
131 MyMutex::MyLock cropLock(cropMutex);
132 return cropImageListener;
133 }
134
update(int todo)135 void Crop::update(int todo)
136 {
137 MyMutex::MyLock cropLock(cropMutex);
138
139 ProcParams& params = parent->params;
140 // CropGUIListener* cropgl;
141
142 // No need to update todo here, since it has already been changed in ImprocCoordinator::updatePreviewImage,
143 // and Crop::update ask to do ALL anyway
144
145 // give possibility to the listener to modify crop window (as the full image dimensions are already known at this point)
146 int wx, wy, ww, wh, ws;
147 const bool overrideWindow = cropImageListener;
148 bool spotsDone = false;
149
150 if (overrideWindow) {
151 cropImageListener->getWindow(wx, wy, ww, wh, ws);
152 }
153
154 // re-allocate sub-images and arrays if their dimensions changed
155 bool needsinitupdate = false;
156
157 if (!overrideWindow) {
158 needsinitupdate = setCropSizes(rqcropx, rqcropy, rqcropw, rqcroph, skip, true);
159 } else {
160 needsinitupdate = setCropSizes(wx, wy, ww, wh, ws, true); // this set skip=ws
161 }
162
163 // it something has been reallocated, all processing steps have to be performed
164 if (needsinitupdate || (todo & M_HIGHQUAL)) {
165 todo = ALL;
166 }
167
168 // Tells to the ImProcFunctions' tool what is the preview scale, which may lead to some simplifications
169 parent->ipf.setScale(skip);
170 parent->ipf.setPipetteBuffer(this);
171 parent->ipf.setViewport(0, 0, -1, -1);
172 parent->ipf.setOutputHistograms(nullptr, nullptr, nullptr);
173 parent->ipf.setShowSharpeningMask(parent->sharpMask);
174
175 Imagefloat* baseCrop = origCrop;
176
177 bool needstransform = parent->ipf.needsTransform();
178 bool show_denoise = params.denoise.enabled && (skip == 1 || options.denoiseZoomedOut);
179
180 if (todo & (M_INIT | M_LINDENOISE | M_HDR)) {
181 MyMutex::MyLock lock(parent->minit); // Also used in improccoord
182
183 int tr = getCoarseBitMask(params.coarse);
184
185 if (!needsinitupdate) {
186 setCropSizes(rqcropx, rqcropy, rqcropw, rqcroph, skip, true);
187 }
188
189 PreviewProps pp(trafx, trafy, trafw * skip, trafh * skip, skip);
190 parent->imgsrc->getImage(parent->currWB, tr, origCrop, pp, params.exposure, params.raw);
191
192 if (show_denoise) {
193 parent->ipf.denoiseComputeParams(parent->imgsrc, parent->currWB, parent->denoiseInfoStore, params.denoise);
194 }
195
196 if ((!isDetailWindow) && parent->adnListener && show_denoise) {
197 parent->adnListener->chromaChanged(params.denoise.chrominance, params.denoise.chrominanceRedGreen, params.denoise.chrominanceBlueYellow);
198 }
199
200
201 if ((todo & M_SPOT) && params.spot.enabled && !params.spot.entries.empty()) {
202 spotsDone = true;
203 PreviewProps pp(trafx, trafy, trafw * skip, trafh * skip, skip);
204 //parent->imgsrc->getImage(parent->currWB, tr, origCrop, pp, params.toneCurve, params.raw);
205 parent->ipf.removeSpots(origCrop, parent->imgsrc, params.spot.entries, pp, parent->currWB, nullptr, tr);
206 }
207
208 parent->imgsrc->convertColorSpace(origCrop, params.icm, parent->currWB);
209
210 if ((todo & M_LINDENOISE) && show_denoise) {
211 parent->ipf.denoise(parent->imgsrc, parent->currWB, origCrop, parent->denoiseInfoStore, params.denoise);
212
213 if (parent->adnListener && params.denoise.chrominanceMethod == DenoiseParams::ChrominanceMethod::AUTOMATIC) {
214 parent->adnListener->chromaChanged(params.denoise.chrominance, params.denoise.chrominanceRedGreen, params.denoise.chrominanceBlueYellow);
215 }
216 }
217 }
218
219 // has to be called after setCropSizes! Tools prior to this point can't handle the Edit mechanism, but that shouldn't be a problem.
220 createBuffer(cropw, croph);
221
222 int offset_x = cropx / skip;
223 int offset_y = cropy / skip;
224 int full_width = parent->getFullWidth() / skip;
225 int full_height = parent->getFullHeight() / skip;
226 parent->ipf.setViewport(offset_x, offset_y, full_width, full_height);
227
228 // Apply Spot removal
229 if ((todo & M_SPOT) && !spotsDone) {
230 if (params.spot.enabled && !params.spot.entries.empty()) {
231 if(!spotCrop) {
232 spotCrop = new Imagefloat (cropw, croph);
233 }
234 baseCrop->copyTo(spotCrop);
235 PreviewProps pp (trafx, trafy, trafw * skip, trafh * skip, skip);
236 int tr = getCoarseBitMask(params.coarse);
237 parent->ipf.removeSpots (spotCrop, parent->imgsrc, params.spot.entries, pp, parent->currWB, ¶ms.icm, tr);
238 } else {
239 if (spotCrop) {
240 delete spotCrop;
241 spotCrop = nullptr;
242 }
243 }
244 }
245
246 if (spotCrop) {
247 baseCrop = spotCrop;
248 }
249
250 std::unique_ptr<Imagefloat> drCompCrop;
251 bool stop = false;
252
253 if ((todo & M_HDR) && (params.fattal.enabled || params.dehaze.enabled)) {
254 Imagefloat *f = origCrop;
255 int fw = skips(parent->fw, skip);
256 int fh = skips(parent->fh, skip);
257 bool need_cropping = false;
258 bool need_drcomp = true;
259
260 if (trafx || trafy || trafw != fw || trafh != fh) {
261 need_cropping = true;
262 const bool copy_from_earlier_steps = params.denoise.enabled || params.spot.enabled;
263
264 // fattal needs to work on the full image. So here we get the full
265 // image from imgsrc, and replace the denoised crop in case
266 if (!copy_from_earlier_steps && skip == 1 && parent->drcomp_11_dcrop_cache) {
267 f = parent->drcomp_11_dcrop_cache;
268 need_drcomp = false;
269 pipeline_stop_[0] = parent->pipeline_stop_[0];
270 } else {
271 f = new Imagefloat(fw, fh, origCrop);
272 drCompCrop.reset(f);
273 PreviewProps pp(0, 0, parent->fw, parent->fh, skip);
274 int tr = getCoarseBitMask(params.coarse);
275 parent->imgsrc->getImage(parent->currWB, tr, f, pp, params.exposure, params.raw);
276 parent->imgsrc->convertColorSpace(f, params.icm, parent->currWB);
277
278 if (copy_from_earlier_steps) {
279 // copy the denoised crop
280 int oy = trafy / skip;
281 int ox = trafx / skip;
282 #ifdef _OPENMP
283 #pragma omp parallel for
284 #endif
285
286 for (int y = 0; y < baseCrop->getHeight(); ++y) {
287 int dy = oy + y;
288
289 for (int x = 0; x < baseCrop->getWidth(); ++x) {
290 int dx = ox + x;
291 f->r(dy, dx) = baseCrop->r(y, x);
292 f->g(dy, dx) = baseCrop->g(y, x);
293 f->b(dy, dx) = baseCrop->b(y, x);
294 }
295 }
296 } else if (skip == 1) {
297 parent->drcomp_11_dcrop_cache = f; // cache this globally
298 drCompCrop.release();
299 }
300 }
301 }
302
303 if (need_drcomp) {
304 pipeline_stop_[0] = parent->ipf.process(ImProcFunctions::Pipeline::PREVIEW, ImProcFunctions::Stage::STAGE_0, f);
305 }
306 stop = pipeline_stop_[0];
307
308 // crop back to the size expected by the rest of the pipeline
309 if (need_cropping) {
310 Imagefloat *c = origCrop;
311
312 int oy = trafy / skip;
313 int ox = trafx / skip;
314 #ifdef _OPENMP
315 #pragma omp parallel for
316 #endif
317
318 for (int y = 0; y < trafh; ++y) {
319 int cy = y + oy;
320
321 for (int x = 0; x < trafw; ++x) {
322 int cx = x + ox;
323 c->r(y, x) = f->r(cy, cx);
324 c->g(y, x) = f->g(cy, cx);
325 c->b(y, x) = f->b(cy, cx);
326 }
327 }
328
329 baseCrop = c;
330 } else {
331 baseCrop = f;
332 }
333 }
334
335 // transform
336 if (needstransform) {
337 if (!transCrop) {
338 transCrop = new Imagefloat(cropw, croph, baseCrop);
339 }
340
341 if (needstransform) {
342 parent->ipf.transform(baseCrop, transCrop, cropx / skip, cropy / skip, trafx / skip, trafy / skip, skips(parent->fw, skip), skips(parent->fh, skip), parent->getFullWidth(), parent->getFullHeight(),
343 parent->imgsrc->getMetaData(),
344 parent->imgsrc->getRotateDegree(), false);
345 } else {
346 baseCrop->copyTo(transCrop);
347 }
348
349 if (transCrop) {
350 baseCrop = transCrop;
351 }
352 } else {
353 if (transCrop) {
354 delete transCrop;
355 }
356
357 transCrop = nullptr;
358 }
359
360 // int offset_x = cropx / skip;
361 // int offset_y = cropy / skip;
362 // int full_width = parent->getFullWidth() / skip;
363 // int full_height = parent->getFullHeight() / skip;
364 // parent->ipf.setViewport(offset_x, offset_y, full_width, full_height);
365
366 if (todo & M_RGBCURVE) {
367 Imagefloat *workingCrop = baseCrop;
368 workingCrop->copyTo(bufs_[0]);
369 pipeline_stop_[1] = stop || parent->ipf.process(ImProcFunctions::Pipeline::PREVIEW, ImProcFunctions::Stage::STAGE_1, bufs_[0]);
370
371 if (workingCrop != baseCrop) {
372 delete workingCrop;
373 }
374 }
375 stop = stop || pipeline_stop_[1];
376
377 if (todo & M_LUMACURVE) {
378 bufs_[0]->copyTo(bufs_[1]);
379
380 pipeline_stop_[2] = stop || parent->ipf.process(ImProcFunctions::Pipeline::PREVIEW, ImProcFunctions::Stage::STAGE_2, bufs_[1]);
381 }
382 stop = stop || pipeline_stop_[2];
383
384 if (todo & (M_LUMINANCE | M_COLOR)) {
385 bufs_[1]->copyTo(bufs_[2]);
386
387 pipeline_stop_[3] = stop || parent->ipf.process(ImProcFunctions::Pipeline::PREVIEW, ImProcFunctions::Stage::STAGE_3, bufs_[2]);
388 }
389 stop = stop || pipeline_stop_[3];
390
391 // all pipette buffer processing should be finished now
392 PipetteBuffer::setReady();
393
394 // Computing the preview image, i.e. converting from lab->Monitor color space (soft-proofing disabled) or lab->Output profile->Monitor color space (soft-proofing enabled)
395 parent->ipf.rgb2monitor(bufs_[2], cropImg);
396
397 if (cropImageListener) {
398 // Computing the internal image for analysis, i.e. conversion from lab->Output profile (rtSettings.HistogramWorking disabled) or lab->WCS (rtSettings.HistogramWorking enabled)
399
400 // internal image in output color space for analysis
401 Image8 *cropImgtrue = parent->ipf.rgb2out(bufs_[2], 0, 0, cropImg->getWidth(), cropImg->getHeight(), params.icm);
402
403 int finalW = rqcropw;
404
405 if (cropImg->getWidth() - leftBorder < finalW) {
406 finalW = cropImg->getWidth() - leftBorder;
407 }
408
409 int finalH = rqcroph;
410
411 if (cropImg->getHeight() - upperBorder < finalH) {
412 finalH = cropImg->getHeight() - upperBorder;
413 }
414
415 Image8* final = new Image8(finalW, finalH);
416 Image8* finaltrue = new Image8(finalW, finalH);
417
418 const int cW = cropImg->getWidth();
419
420 for (int i = 0; i < finalH; i++) {
421 memcpy(final->data + 3 * i * finalW, cropImg->data + 3 * (i + upperBorder)*cW + 3 * leftBorder, 3 * finalW);
422 memcpy(finaltrue->data + 3 * i * finalW, cropImgtrue->data + 3 * (i + upperBorder)*cW + 3 * leftBorder, 3 * finalW);
423 }
424
425 cropImageListener->setDetailedCrop(final, finaltrue, params.icm, params.crop, rqcropx, rqcropy, rqcropw, rqcroph, skip);
426 delete final;
427 delete finaltrue;
428 delete cropImgtrue;
429 }
430 }
431
freeAll()432 void Crop::freeAll()
433 {
434
435 if (cropAllocated) {
436 if (origCrop) {
437 delete origCrop;
438 origCrop = nullptr;
439 }
440
441 if (transCrop) {
442 delete transCrop;
443 transCrop = nullptr;
444 }
445
446 for (int i = 3; i > 0; --i) {
447 if (bufs_[i-1]) {
448 delete bufs_[i-1];
449 bufs_[i-1] = nullptr;
450 }
451 }
452
453 if (cropImg) {
454 delete cropImg;
455 cropImg = nullptr;
456 }
457
458 PipetteBuffer::flush();
459 }
460
461 cropAllocated = false;
462 }
463
464
465 namespace {
466
check_need_larger_crop_for_transform(int fw,int fh,int x,int y,int w,int h,const ProcParams & params,double & adjust)467 bool check_need_larger_crop_for_transform(int fw, int fh, int x, int y, int w, int h, const ProcParams ¶ms, double &adjust)
468 {
469 if (x == 0 && y == 0 && w == fw && h == fh) {
470 return false;
471 }
472
473 if (params.perspective.enabled) {
474 adjust = 1; // TODO -- ask PerspectiveCorrection the right value
475 return true;
476 } else if (params.lensProf.useDist && params.lensProf.needed()) {
477 adjust = 0.15;
478 return true;
479 }
480
481 return false;
482 }
483
484 } // namespace
485
486 /** @brief Handles crop's image buffer reallocation and trigger sizeChanged of SizeListener[s]
487 * If the scale changes, this method will free all buffers and reallocate ones of the new size.
488 * It will then tell to the SizeListener that size has changed (sizeChanged)
489 */
setCropSizes(int rcx,int rcy,int rcw,int rch,int skip,bool internal)490 bool Crop::setCropSizes(int rcx, int rcy, int rcw, int rch, int skip, bool internal)
491 {
492
493 if (!internal) {
494 cropMutex.lock();
495 }
496
497 bool changed = false;
498
499 rqcropx = rcx;
500 rqcropy = rcy;
501 rqcropw = rcw;
502 rqcroph = rch;
503
504 // store and set requested crop size
505 int rqx1 = LIM(rqcropx, 0, parent->fullw - 1);
506 int rqy1 = LIM(rqcropy, 0, parent->fullh - 1);
507 int rqx2 = rqx1 + rqcropw - 1;
508 int rqy2 = rqy1 + rqcroph - 1;
509 rqx2 = LIM(rqx2, 0, parent->fullw - 1);
510 rqy2 = LIM(rqy2, 0, parent->fullh - 1);
511
512 this->skip = skip;
513
514 // add border, if possible
515 int bx1 = rqx1 - skip * borderRequested;
516 int by1 = rqy1 - skip * borderRequested;
517 int bx2 = rqx2 + skip * borderRequested;
518 int by2 = rqy2 + skip * borderRequested;
519 // clip it to fit into image area
520 bx1 = LIM(bx1, 0, parent->fullw - 1);
521 by1 = LIM(by1, 0, parent->fullh - 1);
522 bx2 = LIM(bx2, 0, parent->fullw - 1);
523 by2 = LIM(by2, 0, parent->fullh - 1);
524 int bw = bx2 - bx1 + 1;
525 int bh = by2 - by1 + 1;
526
527 // determine which part of the source image is required to compute the crop rectangle
528 int orx, ory, orw, orh;
529 orx = bx1;
530 ory = by1;
531 orw = bw;
532 orh = bh;
533
534 parent->ipf.transCoord(parent->fw, parent->fh, bx1, by1, bw, bh, orx, ory, orw, orh);
535
536 double adjust = 0.f;
537 if (check_need_larger_crop_for_transform(parent->fw, parent->fh, orx, ory, orw, orh, parent->params, adjust)) {
538 // TODO - "adjust" is an estimate of the max distortion relative to the image size. It is hardcoded to be 15% for lens distortion correction, and 100% for perspective (because we don't know better yet -- ideally this should be calculated)
539 int dW = int (double (parent->fw) * adjust / 2);//(2 * skip));
540 int dH = int (double (parent->fh) * adjust / 2);//(2 * skip));
541 int x1 = orx - dW;
542 int x2 = orx + orw + dW;
543 int y1 = ory - dH;
544 int y2 = ory + orh + dH;
545
546 if (x1 < 0) {
547 x2 += -x1;
548 x1 = 0;
549 }
550
551 if (x2 > parent->fw) {
552 x1 -= x2 - parent->fw;
553 x2 = parent->fw;
554 }
555
556 if (y1 < 0) {
557 y2 += -y1;
558 y1 = 0;
559 }
560
561 if (y2 > parent->fh) {
562 y1 -= y2 - parent->fh;
563 y2 = parent->fh;
564 }
565
566 orx = max(x1, 0);
567 ory = max(y1, 0);
568 orw = min(x2 - x1, parent->fw - orx);
569 orh = min(y2 - y1, parent->fh - ory);
570 }
571
572 leftBorder = skips(rqx1 - bx1, skip);
573 upperBorder = skips(rqy1 - by1, skip);
574
575 PreviewProps cp(orx, ory, orw, orh, skip);
576 int orW, orH;
577 parent->imgsrc->getSize(cp, orW, orH);
578
579 trafx = orx;
580 trafy = ory;
581
582 int cw = skips(bw, skip);
583 int ch = skips(bh, skip);
584
585 EditType editType = ET_PIPETTE;
586
587 if (const auto editProvider = PipetteBuffer::getDataProvider()) {
588 if (const auto editSubscriber = editProvider->getCurrSubscriber()) {
589 editType = editSubscriber->getEditingType();
590 }
591 }
592
593 if (cw != cropw || ch != croph || orW != trafw || orH != trafh) {
594
595 cropw = cw;
596 croph = ch;
597 trafw = orW;
598 trafh = orH;
599
600 if (!origCrop) {
601 origCrop = new Imagefloat;
602 }
603
604 origCrop->allocate(trafw, trafh); // Resizing the buffer (optimization)
605
606 // if transCrop doesn't exist yet, it'll be created where necessary
607 if (transCrop) {
608 transCrop->allocate(cropw, croph);
609 }
610
611 for (int i = 0; i < 3; ++i) {
612 if (bufs_[i]) {
613 delete bufs_[i];
614 }
615 bufs_[i] = new Imagefloat(cropw, croph);
616 }
617
618 if (!cropImg) {
619 cropImg = new Image8;
620 }
621
622 cropImg->allocate(cropw, croph); // Resizing the buffer (optimization)
623
624 if (editType == ET_PIPETTE) {
625 PipetteBuffer::resize(cropw, croph);
626 } else if (PipetteBuffer::bufferCreated()) {
627 PipetteBuffer::flush();
628 }
629
630 cropAllocated = true;
631
632 changed = true;
633 }
634
635 origCrop->assignColorSpace(parent->params.icm.workingProfile);
636 if (transCrop) {
637 transCrop->assignColorSpace(parent->params.icm.workingProfile);
638 }
639 for (int i = 0; i < 3; ++i) {
640 bufs_[i]->assignColorSpace(parent->params.icm.workingProfile);
641 }
642
643 cropx = bx1;
644 cropy = by1;
645
646 if (!internal) {
647 cropMutex.unlock();
648 }
649
650 return changed;
651 }
652
653 /** @brief Look out if a new thread has to be started to process the update
654 *
655 * @return If true, a new updating thread has to be created. If false, the current updating thread will be used
656 */
tryUpdate()657 bool Crop::tryUpdate()
658 {
659 bool needsNewThread = true;
660
661 if (updating) {
662 // tells to the updater thread that a new update is pending
663 newUpdatePending = true;
664 // no need for a new thread, the current one will do the job
665 needsNewThread = false;
666 } else
667 // the crop is now being updated ...well, when fullUpdate will be called
668 {
669 updating = true;
670 }
671
672 return needsNewThread;
673 }
674
675 /* @brief Handles Crop updating in its own thread
676 *
677 * This method will cycle updates as long as Crop::newUpdatePending will be true. During the processing,
678 * intermediary update will be automatically flushed by Crop::tryUpdate.
679 *
680 * This method is called when the visible part of the crop has changed (resize, zoom, etc..), so it needs a full update
681 */
fullUpdate()682 void Crop::fullUpdate()
683 {
684
685 parent->updaterThreadStart.lock();
686
687 if (parent->updaterRunning && parent->thread) {
688 // Do NOT reset changes here, since in a long chain of events it will lead to chroma_scale not being updated,
689 // causing Color::lab2rgb to return a black image on some opens
690 //parent->changeSinceLast = 0;
691 parent->thread->join();
692 }
693
694 if (parent->plistener) {
695 parent->plistener->setProgressState(true);
696 parent->ipf.setProgressListener(parent->plistener, 1);
697 }
698
699 // If there are more update request, the following WHILE will collect it
700 newUpdatePending = true;
701
702 if (parent->tweakOperator) {
703 parent->backupParams();
704 parent->tweakOperator->tweakParams(parent->params);
705 }
706 while (newUpdatePending) {
707 newUpdatePending = false;
708 update(ALL);
709 }
710 if (parent->tweakOperator) {
711 parent->restoreParams();
712 }
713
714 updating = false; // end of crop update
715
716 if (parent->plistener) {
717 parent->plistener->setProgressState(false);
718 }
719
720 parent->updaterThreadStart.unlock();
721 }
722
get_skip()723 int Crop::get_skip()
724 {
725 MyMutex::MyLock lock(cropMutex);
726 return skip;
727 }
728
getLeftBorder()729 int Crop::getLeftBorder()
730 {
731 MyMutex::MyLock lock(cropMutex);
732 return leftBorder;
733 }
734
getUpperBorder()735 int Crop::getUpperBorder()
736 {
737 MyMutex::MyLock lock(cropMutex);
738 return upperBorder;
739 }
740
741 }
742