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, &params.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 &params, 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