Krita Source Code Documentation
Loading...
Searching...
No Matches
kis_inpaint.cpp
Go to the documentation of this file.
1/*
2 * SPDX-FileCopyrightText: 2017 Eugene Ingerman
3 *
4 * SPDX-License-Identifier: GPL-2.0-or-later
5 */
6
18#include <boost/multi_array.hpp>
19#include <random>
20#include <iostream>
21#include <functional>
22
23
24#include "kis_paint_device.h"
25#include "kis_painter.h"
26#include "kis_selection.h"
27
28#include "kis_debug.h"
30//#include "kis_random_accessor_ng.h"
31
32#include <QtMath>
33#include <QList>
35#include <kis_filter_strategy.h>
36#include "KoColor.h"
37#include "KoColorSpace.h"
38#include "KoChannelInfo.h"
39#include "KoMixColorsOp.h"
42#include "KoColorSpaceTraits.h"
43
44const int MAX_DIST = 65535;
45const quint8 MASK_SET = 255;
46const quint8 MASK_CLEAR = 0;
47
48class MaskedImage; //forward decl for the forward decl below
49template <typename T> float distance_impl(const MaskedImage& my, int x, int y, const MaskedImage& other, int xo, int yo);
50
51
53{
54
55protected:
56 quint8* m_data {nullptr};
57 int m_imageWidth {0};
59 int m_pixelSize {0};
60
61public:
62 void Init(quint8* _data, int _imageWidth, int _imageHeight, int _pixelSize)
63 {
64 m_data = _data;
65 m_imageWidth = _imageWidth;
66 m_imageHeight = _imageHeight;
67 m_pixelSize = _pixelSize;
68 }
69
70 ImageView() : m_data(nullptr)
71
72 {
74 }
75
76
77 ImageView(quint8* _data, int _imageWidth, int _imageHeight, int _pixelSize)
78 {
79 Init(_data, _imageWidth, _imageHeight, _pixelSize);
80 }
81
82 quint8* operator()(int x, int y) const
83 {
84 Q_ASSERT(m_data);
85 Q_ASSERT((x >= 0) && (x < m_imageWidth) && (y >= 0) && (y < m_imageHeight));
86 return (m_data + x * m_pixelSize + y * m_imageWidth * m_pixelSize);
87 }
88
90 {
91 if (this != &other) {
92 if (other.num_bytes() != num_bytes()) {
93 delete[] m_data;
94 m_data = nullptr; //to preserve invariance if next line throws exception
95 m_data = new quint8[other.num_bytes()];
96
97 }
98 std::copy(other.data(), other.data() + other.num_bytes(), m_data);
101 m_pixelSize = other.m_pixelSize;
102 }
103 return *this;
104 }
105
106 //move assignment operator
107 ImageView& operator=(ImageView&& other) noexcept
108 {
109 if (this != &other) {
110 delete[] m_data;
111 m_data = nullptr;
112 Init(other.data(), other.m_imageWidth, other.m_imageHeight, other.m_pixelSize);
113 other.m_data = nullptr;
114 }
115 return *this;
116 }
117
118 virtual ~ImageView() {} //this class doesn't own m_data, so it ain't going to delete it either.
119
120 quint8* data(void) const
121 {
122 return m_data;
123 }
124
125 inline int num_elements(void) const
126 {
128 }
129
130 inline int num_bytes(void) const
131 {
133 }
134
135 inline int pixel_size(void) const
136 {
137 return m_pixelSize;
138 }
139
141 {
142 Q_ASSERT(outDev->colorSpace()->pixelSize() == (quint32) m_pixelSize);
143 outDev->writeBytes(m_data, rect);
144 }
145
146 void DebugDump(const QString& fnamePrefix)
147 {
148 QRect imSize(QPoint(0, 0), QSize(m_imageWidth, m_imageHeight));
149 const KoColorSpace* cs = (m_pixelSize == 1) ?
151 KoColorSpaceRegistry::instance()->colorSpace("RGBA", "U8", "");
152 KisPaintDeviceSP dbout = new KisPaintDevice(cs);
153 saveToDevice(dbout, imSize);
154 KIS_DUMP_DEVICE_2(dbout, imSize, fnamePrefix, "./");
155 }
156};
157
158class ImageData : public ImageView
159{
160
161public:
163
164 void Init(int _imageWidth, int _imageHeight, int _pixelSize)
165 {
166 m_data = new quint8[ _imageWidth * _imageHeight * _pixelSize ];
167 ImageView::Init(m_data, _imageWidth, _imageHeight, _pixelSize);
168 }
169
170 ImageData(int _imageWidth, int _imageHeight, int _pixelSize) : ImageView()
171 {
172 Init(_imageWidth, _imageHeight, _pixelSize);
173 }
174
175 void Init(KisPaintDeviceSP imageDev, const QRect& imageSize)
176 {
177 const KoColorSpace* cs = imageDev->colorSpace();
178 m_pixelSize = cs->pixelSize();
179
180 m_data = new quint8[ imageSize.width()*imageSize.height()*cs->pixelSize() ];
181 imageDev->readBytes(m_data, imageSize.x(), imageSize.y(), imageSize.width(), imageSize.height());
182 ImageView::Init(m_data, imageSize.width(), imageSize.height(), m_pixelSize);
183 }
184
185 ImageData(KisPaintDeviceSP imageDev, const QRect& imageSize) : ImageView()
186 {
187 Init(imageDev, imageSize);
188 }
189
190 ~ImageData() override
191 {
192 delete[] m_data; //ImageData owns m_data, so it has to delete it
193 }
194
195};
196
197
198
199class MaskedImage : public KisShared
200{
201private:
202
203 template <typename T> friend float distance_impl(const MaskedImage& my, int x, int y, const MaskedImage& other, int xo, int yo);
204
206 int nChannels {0};
207
208 const KoColorSpace* cs {nullptr};
209 const KoColorSpace* csMask {nullptr};
210
213
214
215 void cacheImage(KisPaintDeviceSP imageDev, QRect rect)
216 {
217 cs = imageDev->colorSpace();
219 imageData.Init(imageDev, rect);
220 imageSize = rect;
221 }
222
223
224 void cacheMask(KisPaintDeviceSP maskDev, QRect rect)
225 {
226 Q_ASSERT(maskDev->colorSpace()->pixelSize() == 1);
227 csMask = maskDev->colorSpace();
228 maskData.Init(maskDev, rect);
229
230 //hard threshold for the initial mask
231 //may be optional. needs testing
232 std::for_each(maskData.data(), maskData.data() + maskData.num_bytes(), [](quint8 & v) {
233 v = (v > MASK_CLEAR) ? MASK_SET : MASK_CLEAR;
234 });
235 }
236
238
239public:
240 std::function< float(const MaskedImage&, int, int, const MaskedImage& , int , int ) > distance;
241
242 void toPaintDevice(KisPaintDeviceSP imageDev, QRect rect, KisSelectionSP selection)
243 {
244 if (!selection) {
245 imageData.saveToDevice(imageDev, rect);
246 } else {
247 KisPaintDeviceSP dev = new KisPaintDevice(imageDev->colorSpace());
248 dev->setDefaultBounds(imageDev->defaultBounds());
249
251
252 KisPainter::copyAreaOptimized(rect.topLeft(), dev, imageDev, rect, selection);
253 }
254 }
255
256 void DebugDump(const QString& name)
257 {
258 imageData.DebugDump(name + "_img");
259 maskData.DebugDump(name + "_mask");
260 }
261
262 void clearMask(void)
263 {
265 }
266
267 void initialize(KisPaintDeviceSP _imageDev, KisPaintDeviceSP _maskDev, QRect _maskRect)
268 {
269 cacheImage(_imageDev, _maskRect);
270 cacheMask(_maskDev, _maskRect);
271
272 //distance function is the only that needs to know the type
273 //For performance reasons we can't use functions provided by color space
274 KoID colorDepthId = _imageDev->colorSpace()->colorDepthId();
275
276 //Use RGB traits to assign actual pixel data types.
277 distance = &distance_impl<KoRgbU8Traits::channels_type>;
278
279 if( colorDepthId == Integer16BitsColorDepthID )
280 distance = &distance_impl<KoRgbU16Traits::channels_type>;
281#ifdef HAVE_OPENEXR
282 if( colorDepthId == Float16BitsColorDepthID )
283 distance = &distance_impl<KoRgbF16Traits::channels_type>;
284#endif
285 if( colorDepthId == Float32BitsColorDepthID )
286 distance = &distance_impl<KoRgbF32Traits::channels_type>;
287
288 if( colorDepthId == Float64BitsColorDepthID )
289 distance = &distance_impl<KoRgbF64Traits::channels_type>;
290 }
291
292 MaskedImage(KisPaintDeviceSP _imageDev, KisPaintDeviceSP _maskDev, QRect _maskRect)
293 {
294 initialize(_imageDev, _maskDev, _maskRect);
295 }
296
297 void downsample2x(void)
298 {
299 int H = imageSize.height();
300 int W = imageSize.width();
301 int newW = W / 2, newH = H / 2;
302
303 KisPaintDeviceSP imageDev = new KisPaintDevice(cs);
305 imageDev->writeBytes(imageData.data(), 0, 0, W, H);
306 maskDev->writeBytes(maskData.data(), 0, 0, W, H);
307
308 ImageData newImage(newW, newH, cs->pixelSize());
309 ImageData newMask(newW, newH, 1);
310
311 KoDummyUpdaterHolder updaterHolder;
312 KisTransformWorker worker(imageDev, 1. / 2., 1. / 2., 0.0, 0.0, 0.0, 0.0, 0.0,
313 updaterHolder.updater(), KisFilterStrategyRegistry::instance()->value("Bicubic"));
314 worker.run();
315
316 KisTransformWorker workerMask(maskDev, 1. / 2., 1. / 2., 0.0, 0.0, 0.0, 0.0, 0.0,
317 updaterHolder.updater(), KisFilterStrategyRegistry::instance()->value("Bicubic"));
318 workerMask.run();
319
320 imageDev->readBytes(newImage.data(), 0, 0, newW, newH);
321 maskDev->readBytes(newMask.data(), 0, 0, newW, newH);
322 imageData = std::move(newImage);
323 maskData = std::move(newMask);
324
325 for (int i = 0; i < imageData.num_elements(); ++i) {
326 quint8* maskPix = maskData.data() + i * maskData.pixel_size();
327 if (*maskPix == MASK_SET) {
328 for (int k = 0; k < imageData.pixel_size(); k++)
329 *(imageData.data() + i * imageData.pixel_size() + k) = 0;
330 } else {
331 *maskPix = MASK_CLEAR;
332 }
333 }
334 imageSize = QRect(0, 0, newW, newH);
335 }
336
337 void upscale(int newW, int newH)
338 {
339 int H = imageSize.height();
340 int W = imageSize.width();
341
342 ImageData newImage(newW, newH, cs->pixelSize());
343 ImageData newMask(newW, newH, 1);
344
345 QVector<float> colors(nChannels, 0.f);
347
348 for (int y = 0; y < newH; ++y) {
349 for (int x = 0; x < newW; ++x) {
350
351 // original pixel
352 int xs = (x * W) / newW;
353 int ys = (y * H) / newH;
354
355 // copy to new image
356 if (!isMasked(xs, ys)) {
357 std::copy(imageData(xs, ys), imageData(xs, ys) + imageData.pixel_size(), newImage(x, y));
358 *newMask(x, y) = MASK_CLEAR;
359 } else {
360 std::fill(newImage(x, y), newImage(x, y) + newImage.pixel_size(), 0);
361 *newMask(x, y) = MASK_SET;
362 }
363 }
364 }
365
366 imageData = std::move(newImage);
367 maskData = std::move(newMask);
368 imageSize = QRect(0, 0, newW, newH);
369 }
370
371 QRect size()
372 {
373 return imageSize;
374 }
375
377 {
379 clone->imageSize = this->imageSize;
380 clone->nChannels = this->nChannels;
381 clone->maskData = this->maskData;
382 clone->imageData = this->imageData;
383 clone->cs = this->cs;
384 clone->csMask = this->csMask;
385 clone->distance = this->distance;
386 return clone;
387 }
388
389 int countMasked(void)
390 {
391 int count = std::count_if(maskData.data(), maskData.data() + maskData.num_elements(), [](quint8 v) {
392 return v > MASK_CLEAR;
393 });
394 return count;
395 }
396
397 inline bool isMasked(int x, int y)
398 {
399 return (*maskData(x, y) > MASK_CLEAR);
400 }
401
402 //returns true if the patch contains a masked pixel
403 bool containsMasked(int x, int y, int S)
404 {
405 for (int dy = -S; dy <= S; ++dy) {
406 int ys = y + dy;
407 if (ys < 0 || ys >= imageSize.height())
408 continue;
409
410 for (int dx = -S; dx <= S; ++dx) {
411 int xs = x + dx;
412 if (xs < 0 || xs >= imageSize.width())
413 continue;
414 if (isMasked(xs, ys))
415 return true;
416 }
417 }
418 return false;
419 }
420
421 inline quint8 getImagePixelU8(int x, int y, int chan) const
422 {
423 return cs->scaleToU8(imageData(x, y), chan);
424 }
425
426 inline QVector<float> getImagePixels(int x, int y) const
427 {
430 return v;
431 }
432
433 inline quint8* getImagePixel(int x, int y)
434 {
435 return imageData(x, y);
436 }
437
438 inline void setImagePixels(int x, int y, QVector<float>& value)
439 {
441 }
442
443 inline void mixColors(std::vector< quint8* > pixels, std::vector< float > w, float wsum, quint8* dst)
444 {
445 const KoMixColorsOp* mixOp = cs->mixColorsOp();
446
447 size_t n = w.size();
448 assert(pixels.size() == n);
449 std::vector< qint16 > weights;
450 weights.clear();
451
452 float dif = 0;
453
454 float scale = 255 / (wsum + 0.001);
455
456 for (auto& v : w) {
457 //compensated summation to increase accuracy
458 float v1 = v * scale + dif;
459 float v2 = std::round(v1);
460 dif = v1 - v2;
461 weights.push_back(v2);
462 }
463
464 mixOp->mixColors(pixels.data(), weights.data(), n, dst);
465 }
466
467 inline void setMask(int x, int y, quint8 v)
468 {
469 *(maskData(x, y)) = v;
470 }
471
472 inline int channelCount(void) const
473 {
474 return cs->channelCount();
475 }
476};
477
478
479//Generic version of the distance function. produces distance between colors in the range [0, MAX_DIST]. This
480//is a fast distance computation. More accurate, but very slow implementation is to use color space operations.
481template <typename T> float distance_impl(const MaskedImage& my, int x, int y, const MaskedImage& other, int xo, int yo)
482{
483 float dsq = 0;
484 quint32 nchannels = my.channelCount();
485 T *v1 = reinterpret_cast<T*>(my.imageData(x, y));
486 T *v2 = reinterpret_cast<T*>(other.imageData(xo, yo));
487
488 for (quint32 chan = 0; chan < nchannels; chan++) {
489 //It's very important not to lose precision in the next line
490 float v = ((float)(*(v1 + chan)) - (float)(*(v2 + chan)));
491 dsq += v * v;
492 }
493
494 // in HDR color spaces the value of the channel may become bigger than the unitValue
495 return qMin((float)(nchannels * MAX_DIST), dsq / (pow2((float)KoColorSpaceMathsTraits<T>::unitValue) / MAX_DIST ));
496}
497
498
500
501struct NNPixel {
502 int x;
503 int y;
505};
506typedef boost::multi_array<NNPixel, 2> NNArray_type;
507
512typedef boost::multi_array<Vote_elem, 2> Vote_type;
513
514
515
517{
518
519private:
520 template< typename T> T randomInt(T range)
521 {
522 return rand() % range;
523 }
524
525 //compute initial value of the distance term
526 void initialize(void)
527 {
528 for (int y = 0; y < imSize.height(); y++) {
529 for (int x = 0; x < imSize.width(); x++) {
530 field[x][y].distance = distance(x, y, field[x][y].x, field[x][y].y);
531
532 //if the distance is "infinity", try to find a better link
533 int iter = 0;
534 const int maxretry = 20;
535 while (field[x][y].distance == MAX_DIST && iter < maxretry) {
536 field[x][y].x = randomInt(imSize.width() + 1);
537 field[x][y].y = randomInt(imSize.height() + 1);
538 field[x][y].distance = distance(x, y, field[x][y].x, field[x][y].y);
539 iter++;
540 }
541 }
542 }
543 }
544
546 {
547 float s_zero = 0.999;
548 float t_halfmax = 0.10;
549
550 float x = (s_zero - 0.5) * 2;
551 float invtanh = 0.5 * std::log((1. + x) / (1. - x));
552 float coef = invtanh / t_halfmax;
553
554 similarity.resize(MAX_DIST + 1);
555 for (int i = 0; i < (int)similarity.size(); i++) {
556 float t = (float)i / similarity.size();
557 similarity[i] = 0.5 - 0.5 * std::tanh(coef * (t - t_halfmax));
558 }
559 }
560
561
562private:
563 int patchSize; //patch size
564public:
567 QRect imSize;
569 std::vector<float> similarity;
570 quint32 nColors;
572
573public:
574 NearestNeighborField(const MaskedImageSP _input, MaskedImageSP _output, int _patchsize) : patchSize(_patchsize), input(_input), output(_output)
575 {
576 imSize = input->size();
577 field.resize(boost::extents[imSize.width()][imSize.height()]);
579
580 nColors = input->channelCount(); //only color count, doesn't include alpha channels
581 }
582
583 void randomize(void)
584 {
585 for (int y = 0; y < imSize.height(); y++) {
586 for (int x = 0; x < imSize.width(); x++) {
587 field[x][y].x = randomInt(imSize.width() + 1);
588 field[x][y].y = randomInt(imSize.height() + 1);
589 field[x][y].distance = MAX_DIST;
590 }
591 }
592 initialize();
593 }
594
595 //initialize field from an existing (possibly smaller) nearest neighbor field
597 {
598 float xscale = qreal(imSize.width()) / nnf.imSize.width();
599 float yscale = qreal(imSize.height()) / nnf.imSize.height();
600
601 for (int y = 0; y < imSize.height(); y++) {
602 for (int x = 0; x < imSize.width(); x++) {
603 int xlow = std::min((int)(x / xscale), nnf.imSize.width() - 1);
604 int ylow = std::min((int)(y / yscale), nnf.imSize.height() - 1);
605
606 field[x][y].x = nnf.field[xlow][ylow].x * xscale;
607 field[x][y].y = nnf.field[xlow][ylow].y * yscale;
608 field[x][y].distance = MAX_DIST;
609 }
610 }
611 initialize();
612 }
613
614 //multi-pass NN-field minimization (see "PatchMatch" paper referenced above - page 4)
615 void minimize(int pass)
616 {
617 int min_x = 0;
618 int min_y = 0;
619 int max_x = imSize.width() - 1;
620 int max_y = imSize.height() - 1;
621
622 for (int i = 0; i < pass; i++) {
623 //scanline order
624 for (int y = min_y; y < max_y; y++)
625 for (int x = min_x; x <= max_x; x++)
626 if (field[x][y].distance > 0)
627 minimizeLink(x, y, 1);
628
629 //reverse scanline order
630 for (int y = max_y; y >= min_y; y--)
631 for (int x = max_x; x >= min_x; x--)
632 if (field[x][y].distance > 0)
633 minimizeLink(x, y, -1);
634 }
635 }
636
637 void minimizeLink(int x, int y, int dir)
638 {
639 int xp, yp, dp;
640
641 //Propagation Left/Right
642 if (x - dir > 0 && x - dir < imSize.width()) {
643 xp = field[x - dir][y].x + dir;
644 yp = field[x - dir][y].y;
645 dp = distance(x, y, xp, yp);
646 if (dp < field[x][y].distance) {
647 field[x][y].x = xp;
648 field[x][y].y = yp;
649 field[x][y].distance = dp;
650 }
651 }
652
653 //Propagation Up/Down
654 if (y - dir > 0 && y - dir < imSize.height()) {
655 xp = field[x][y - dir].x;
656 yp = field[x][y - dir].y + dir;
657 dp = distance(x, y, xp, yp);
658 if (dp < field[x][y].distance) {
659 field[x][y].x = xp;
660 field[x][y].y = yp;
661 field[x][y].distance = dp;
662 }
663 }
664
665 //Random search
666 int wi = std::max(output->size().width(), output->size().height());
667 int xpi = field[x][y].x;
668 int ypi = field[x][y].y;
669 while (wi > 0) {
670 xp = xpi + randomInt(2 * wi) - wi;
671 yp = ypi + randomInt(2 * wi) - wi;
672 xp = std::max(0, std::min(output->size().width() - 1, xp));
673 yp = std::max(0, std::min(output->size().height() - 1, yp));
674
675 dp = distance(x, y, xp, yp);
676 if (dp < field[x][y].distance) {
677 field[x][y].x = xp;
678 field[x][y].y = yp;
679 field[x][y].distance = dp;
680 }
681 wi /= 2;
682 }
683 }
684
685 //compute distance between two patches
686 int distance(int x, int y, int xp, int yp)
687 {
688 qint64 distance = 0;
689 qint64 wsum = 0;
690 const qint64 ssdmax = nColors * 255 * (qint64)255;
691
692 //for each pixel in the source patch
693 for (int dy = -patchSize; dy <= patchSize; dy++) {
694 for (int dx = -patchSize; dx <= patchSize; dx++) {
695 wsum += ssdmax;
696 int xks = x + dx;
697 int yks = y + dy;
698
699 if (xks < 0 || xks >= input->size().width()) {
700 distance += ssdmax;
701 continue;
702 }
703
704 if (yks < 0 || yks >= input->size().height()) {
705 distance += ssdmax;
706 continue;
707 }
708
709 //cannot use masked pixels as a valid source of information
710 if (input->isMasked(xks, yks)) {
711 distance += ssdmax;
712 continue;
713 }
714
715 //corresponding pixel in target patch
716 int xkt = xp + dx;
717 int ykt = yp + dy;
718 if (xkt < 0 || xkt >= output->size().width()) {
719 distance += ssdmax;
720 continue;
721 }
722 if (ykt < 0 || ykt >= output->size().height()) {
723 distance += ssdmax;
724 continue;
725 }
726
727 //cannot use masked pixels as a valid source of information
728 if (output->isMasked(xkt, ykt)) {
729 distance += ssdmax;
730 continue;
731 }
732
733 //SSD distance between pixels
734 float ssd = input->distance(*input, xks, yks, *output, xkt, ykt);
735 distance += qRound(ssd);
736
737 }
738 }
739
740 if (wsum == 0) {
741 return 0; // sanity check, to avoid undefined behaviour in code below
742 }
743 return qFloor(MAX_DIST * (qreal(distance) / wsum));
744 }
745
746 static MaskedImageSP ExpectationMaximization(KisSharedPtr<NearestNeighborField> TargetToSource, int level, int radius, QList<MaskedImageSP>& pyramid);
747
749
750 void EM_Step(MaskedImageSP source, MaskedImageSP target, int R, bool upscaled);
751};
753
754
756{
757private:
764
765
766public:
767 Inpaint(KisPaintDeviceSP dev, KisPaintDeviceSP devMask, int _radius, QRect maskRect)
768 : devCache(dev)
769 , initial(new MaskedImage(dev, devMask, maskRect))
770 , radius(_radius)
771 {
772 }
773 MaskedImageSP patch(void);
775};
776
777
778
780{
782
783 pyramid.append(initial);
784
785 QRect size = source->size();
786
787 //qDebug() << "countMasked: " << source->countMasked() << "\n";
788 while ((size.width() > radius) && (size.height() > radius) && source->countMasked() > 0) {
789 source->downsample2x();
790 //source->DebugDump("Pyramid");
791 //qDebug() << "countMasked1: " << source->countMasked() << "\n";
792 pyramid.append(source->copy());
793 size = source->size();
794 }
795 int maxlevel = pyramid.size();
796 //qDebug() << "MaxLevel: " << maxlevel << "\n";
797
798 // The initial target is the same as the smallest source.
799 // We consider that this target contains no masked pixels
800 MaskedImageSP target = source->copy();
801 target->clearMask();
802
803 //recursively building nearest neighbor field
804 for (int level = maxlevel - 1; level > 0; level--) {
805 source = pyramid.at(level);
806
807 if (level == maxlevel - 1) {
808 //random initial guess
811 } else {
812 // then, we use the rebuilt (upscaled) target
813 // and reuse the previous NNF as initial guess
814
816 new_nnf_rev->initialize(*nnf_TargetToSource);
817 nnf_TargetToSource = new_nnf_rev;
818 }
819
820 //Build an upscaled target by EM-like algorithm (see "PatchMatch" paper referenced above - page 6)
822 //target->DebugDump( "target" );
823 }
824 return target;
825}
826
827
828//EM-Like algorithm (see "PatchMatch" - page 6)
829//Returns a float sized target image
831{
832 int iterEM = std::min(2 * level, 4);
833 int iterNNF = std::min(5, 1 + level);
834
835 MaskedImageSP source = nnf_TargetToSource->output;
836 MaskedImageSP target = nnf_TargetToSource->input;
837 MaskedImageSP newtarget = nullptr;
838
839 //EM loop
840 for (int emloop = 1; emloop <= iterEM; emloop++) {
841 //set the new target as current target
842 if (!newtarget.isNull()) {
843 nnf_TargetToSource->input = newtarget;
844 target = newtarget;
845 newtarget = nullptr;
846 }
847
848 for (int x = 0; x < target->size().width(); ++x) {
849 for (int y = 0; y < target->size().height(); ++y) {
850 if (!source->containsMasked(x, y, radius)) {
851 nnf_TargetToSource->field[x][y].x = x;
852 nnf_TargetToSource->field[x][y].y = y;
853 nnf_TargetToSource->field[x][y].distance = 0;
854 }
855 }
856 }
857
858 //minimize the NNF
859 nnf_TargetToSource->minimize(iterNNF);
860
861 //Now we rebuild the target using best patches from source
862 MaskedImageSP newsource = nullptr;
863 bool upscaled = false;
864
865 // Instead of upsizing the final target, we build the last target from the next level source image
866 // So the final target is less blurry (see "Space-Time Video Completion" - page 5)
867 if (level >= 1 && (emloop == iterEM)) {
868 newsource = pyramid.at(level - 1);
869 QRect sz = newsource->size();
870 newtarget = target->copy();
871 newtarget->upscale(sz.width(), sz.height());
872 upscaled = true;
873 } else {
874 newsource = pyramid.at(level);
875 newtarget = target->copy();
876 upscaled = false;
877 }
878 //EM Step
879
880 //EM_Step(newsource, newtarget, radius, upscaled);
881 ExpectationStep(nnf_TargetToSource, newsource, newtarget, upscaled);
882 }
883
884 return newtarget;
885}
886
887
889{
890 //int*** field = nnf->field;
891 int R = nnf->patchSize;
892 if (upscale)
893 R *= 2;
894
895 int H_nnf = nnf->input->size().height();
896 int W_nnf = nnf->input->size().width();
897 int H_target = target->size().height();
898 int W_target = target->size().width();
899 int H_source = source->size().height();
900 int W_source = source->size().width();
901
902 std::vector< quint8* > pixels;
903 std::vector< float > weights;
904 pixels.reserve(R * R);
905 weights.reserve(R * R);
906 for (int x = 0 ; x < W_target ; ++x) {
907 for (int y = 0 ; y < H_target; ++y) {
908 float wsum = 0;
909 pixels.clear();
910 weights.clear();
911
912
913 if (!source->containsMasked(x, y, R + 4) /*&& upscale*/) {
914 //speedup computation by copying parts that are not masked.
915 pixels.push_back(source->getImagePixel(x, y));
916 weights.push_back(1.f);
917 target->mixColors(pixels, weights, 1.f, target->getImagePixel(x, y));
918 } else {
919 for (int dx = -R ; dx <= R; ++dx) {
920 for (int dy = -R ; dy <= R ; ++dy) {
921 // xpt,ypt = center pixel of the target patch
922 int xpt = x + dx;
923 int ypt = y + dy;
924
925 int xst, yst;
926 float w;
927
928 if (!upscale) {
929 if (xpt < 0 || xpt >= W_nnf || ypt < 0 || ypt >= H_nnf)
930 continue;
931
932 xst = nnf->field[xpt][ypt].x;
933 yst = nnf->field[xpt][ypt].y;
934 int dp = nnf->field[xpt][ypt].distance;
935 // similarity measure between the two patches
936 w = nnf->similarity[dp];
937
938 } else {
939 if (xpt < 0 || (xpt / 2) >= W_nnf || ypt < 0 || (ypt / 2) >= H_nnf)
940 continue;
941 xst = 2 * nnf->field[xpt / 2][ypt / 2].x + (xpt % 2);
942 yst = 2 * nnf->field[xpt / 2][ypt / 2].y + (ypt % 2);
943 int dp = nnf->field[xpt / 2][ypt / 2].distance;
944 // similarity measure between the two patches
945 w = nnf->similarity[dp];
946 }
947
948 int xs = xst - dx;
949 int ys = yst - dy;
950
951 if (xs < 0 || xs >= W_source || ys < 0 || ys >= H_source)
952 continue;
953
954 if (source->isMasked(xs, ys))
955 continue;
956
957 pixels.push_back(source->getImagePixel(xs, ys));
958 weights.push_back(w);
959 wsum += w;
960 }
961 }
962
963 if (wsum < 1)
964 continue;
965
966 target->mixColors(pixels, weights, wsum, target->getImagePixel(x, y));
967 }
968 }
969 }
970}
971
973{
974 QRect maskRect = maskDev->nonDefaultPixelArea();
975 return maskRect;
976}
977
978
979QRect patchImage(const KisPaintDeviceSP imageDev, const KisPaintDeviceSP maskDev, int patchRadius, int accuracy, KisSelectionSP selection)
980{
981 QRect maskRect = getMaskBoundingBox(maskDev);
982 QRect imageRect = imageDev->exactBounds();
983
984 float scale = 1.0 + (accuracy / 25.0); //higher accuracy means we include more surrounding area around the patch. Minimum 2x padding.
985 int dx = maskRect.width() * scale;
986 int dy = maskRect.height() * scale;
987 maskRect.adjust(-dx, -dy, dx, dy);
988 maskRect = maskRect.intersected(imageRect);
989
990 if (!maskRect.isEmpty()) {
991 Inpaint inpaint(imageDev, maskDev, patchRadius, maskRect);
992 MaskedImageSP output = inpaint.patch();
993 output->toPaintDevice(imageDev, maskRect, selection);
994 }
995
996 return maskRect;
997}
998
float value(const T *src, size_t ch)
Eigen::Matrix< double, 4, 2 > S
Eigen::Matrix< double, 4, 2 > R
qreal v
KisMagneticGraph::vertex_descriptor target(typename KisMagneticGraph::edge_descriptor e, KisMagneticGraph g)
KisMagneticGraph::vertex_descriptor source(typename KisMagneticGraph::edge_descriptor e, KisMagneticGraph g)
const KoID Float32BitsColorDepthID("F32", ki18n("32-bit float/channel"))
const KoID Float64BitsColorDepthID("F64", ki18n("64-bit float/channel"))
const KoID Float16BitsColorDepthID("F16", ki18n("16-bit float/channel"))
const KoID Integer16BitsColorDepthID("U16", ki18n("16-bit integer/channel"))
#define upscale(value)
void Init(int _imageWidth, int _imageHeight, int _pixelSize)
ImageData(KisPaintDeviceSP imageDev, const QRect &imageSize)
ImageData(int _imageWidth, int _imageHeight, int _pixelSize)
void Init(KisPaintDeviceSP imageDev, const QRect &imageSize)
~ImageData() override
quint8 * data(void) const
int m_imageHeight
void DebugDump(const QString &fnamePrefix)
int pixel_size(void) const
ImageView & operator=(const ImageView &other)
ImageView & operator=(ImageView &&other) noexcept
quint8 * operator()(int x, int y) const
virtual ~ImageView()
ImageView(quint8 *_data, int _imageWidth, int _imageHeight, int _pixelSize)
void saveToDevice(KisPaintDeviceSP outDev, QRect rect)
void Init(quint8 *_data, int _imageWidth, int _imageHeight, int _pixelSize)
int num_bytes(void) const
int num_elements(void) const
quint8 * m_data
MaskedImageSP initial
MaskedImageSP patch_simple(void)
NearestNeighborFieldSP nnf_SourceToTarget
KisPaintDeviceSP devCache
QList< MaskedImageSP > pyramid
Inpaint(KisPaintDeviceSP dev, KisPaintDeviceSP devMask, int _radius, QRect maskRect)
NearestNeighborFieldSP nnf_TargetToSource
MaskedImageSP patch(void)
static KisFilterStrategyRegistry * instance()
QRect nonDefaultPixelArea() const
void setDefaultBounds(KisDefaultBoundsBaseSP bounds)
QRect exactBounds() const
const KoColorSpace * colorSpace() const
KisDefaultBoundsBaseSP defaultBounds() const
void readBytes(quint8 *data, qint32 x, qint32 y, qint32 w, qint32 h) const
void writeBytes(const quint8 *data, qint32 x, qint32 y, qint32 w, qint32 h)
static void copyAreaOptimized(const QPoint &dstPt, KisPaintDeviceSP src, KisPaintDeviceSP dst, const QRect &originalSrcRect)
bool isNull() const
virtual quint32 pixelSize() const =0
virtual quint8 scaleToU8(const quint8 *srcPixel, qint32 channelPos) const =0
virtual quint32 channelCount() const =0
virtual KoID colorDepthId() const =0
virtual void normalisedChannelsValue(const quint8 *pixel, QVector< float > &channels) const =0
virtual void fromNormalisedChannelsValue(quint8 *pixel, const QVector< float > &values) const =0
KoMixColorsOp * mixColorsOp
A holder for an updater that does nothing.
Definition KoUpdater.h:116
KoUpdater * updater()
const T value(const QString &id) const
Definition KoID.h:30
virtual void mixColors(const quint8 *const *colors, const qint16 *weights, int nColors, quint8 *dst, int weightSum=255) const =0
int channelCount(void) const
const KoColorSpace * cs
friend float distance_impl(const MaskedImage &my, int x, int y, const MaskedImage &other, int xo, int yo)
void toPaintDevice(KisPaintDeviceSP imageDev, QRect rect, KisSelectionSP selection)
void clearMask(void)
void cacheMask(KisPaintDeviceSP maskDev, QRect rect)
ImageData imageData
bool containsMasked(int x, int y, int S)
const KoColorSpace * csMask
void downsample2x(void)
void mixColors(std::vector< quint8 * > pixels, std::vector< float > w, float wsum, quint8 *dst)
void upscale(int newW, int newH)
KisSharedPtr< MaskedImage > copy(void)
void cacheImage(KisPaintDeviceSP imageDev, QRect rect)
quint8 getImagePixelU8(int x, int y, int chan) const
void setMask(int x, int y, quint8 v)
ImageData maskData
QVector< float > getImagePixels(int x, int y) const
bool isMasked(int x, int y)
int countMasked(void)
quint8 * getImagePixel(int x, int y)
void DebugDump(const QString &name)
std::function< float(const MaskedImage &, int, int, const MaskedImage &, int, int) distance)
MaskedImage(KisPaintDeviceSP _imageDev, KisPaintDeviceSP _maskDev, QRect _maskRect)
void initialize(KisPaintDeviceSP _imageDev, KisPaintDeviceSP _maskDev, QRect _maskRect)
void setImagePixels(int x, int y, QVector< float > &value)
void minimizeLink(int x, int y, int dir)
NearestNeighborField(const MaskedImageSP _input, MaskedImageSP _output, int _patchsize)
void minimize(int pass)
static MaskedImageSP ExpectationMaximization(KisSharedPtr< NearestNeighborField > TargetToSource, int level, int radius, QList< MaskedImageSP > &pyramid)
static void ExpectationStep(KisSharedPtr< NearestNeighborField > nnf, MaskedImageSP source, MaskedImageSP target, bool upscale)
void initialize(const NearestNeighborField &nnf)
void init_similarity_curve(void)
int distance(int x, int y, int xp, int yp)
void EM_Step(MaskedImageSP source, MaskedImageSP target, int R, bool upscaled)
std::vector< float > similarity
QList< KoChannelInfo * > channels
T pow2(const T &x)
Definition kis_global.h:166
float distance_impl(const MaskedImage &my, int x, int y, const MaskedImage &other, int xo, int yo)
boost::multi_array< NNPixel, 2 > NNArray_type
const int MAX_DIST
KisSharedPtr< NearestNeighborField > NearestNeighborFieldSP
boost::multi_array< Vote_elem, 2 > Vote_type
QRect getMaskBoundingBox(KisPaintDeviceSP maskDev)
QRect patchImage(const KisPaintDeviceSP imageDev, const KisPaintDeviceSP maskDev, int patchRadius, int accuracy, KisSelectionSP selection)
const quint8 MASK_CLEAR
const quint8 MASK_SET
KisSharedPtr< MaskedImage > MaskedImageSP
#define KIS_DUMP_DEVICE_2(device, rc, suffix, prefix)
const KoColorSpace * colorSpace(const QString &colorModelId, const QString &colorDepthId, const KoColorProfile *profile)
static KoColorSpaceRegistry * instance()
const KoColorSpace * alpha8()
QVector< float > channel_values