781 lines
29 KiB
C++
781 lines
29 KiB
C++
/*
|
|
* This file is part of RawTherapee.
|
|
*
|
|
* Copyright (c) 2004-2010 Gabor Horvath <hgabor@rawtherapee.com>
|
|
*
|
|
* RawTherapee is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* RawTherapee is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with RawTherapee. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "improcfun.h"
|
|
#include "alpha.h"
|
|
#include "procparams.h"
|
|
#include "imagesource.h"
|
|
#include <iostream>
|
|
|
|
namespace
|
|
{
|
|
|
|
// "ceil" rounding
|
|
template<typename T>
|
|
constexpr T skips(T a, T b)
|
|
{
|
|
return a / b + static_cast<bool>(a % b);
|
|
}
|
|
|
|
}
|
|
|
|
namespace rtengine
|
|
{
|
|
|
|
class SpotBox {
|
|
|
|
public:
|
|
enum class Type {
|
|
SOURCE,
|
|
TARGET,
|
|
FINAL
|
|
};
|
|
|
|
struct Rectangle {
|
|
public:
|
|
int x1;
|
|
int y1;
|
|
int x2;
|
|
int y2;
|
|
|
|
Rectangle() : x1(0), y1(0), x2(0), y2(0) {}
|
|
Rectangle(const Rectangle &other) : x1(other.x1), y1(other.y1), x2(other.x2), y2(other.y2) {}
|
|
Rectangle(int X1, int Y1, int X2, int Y2) : x1(X1), y1(Y1), x2(X2), y2(Y2) {}
|
|
|
|
bool intersects(const Rectangle &other) const {
|
|
return (other.x1 <= x2 && other.x2 >= x1)
|
|
&& (other.y1 <= y2 && other.y2 >= y1);
|
|
}
|
|
|
|
bool getIntersection(const Rectangle &other, std::unique_ptr<Rectangle> &intersection) const {
|
|
if (intersects(other)) {
|
|
if (!intersection) {
|
|
intersection.reset(new Rectangle());
|
|
}
|
|
intersection->x1 = rtengine::max(x1, other.x1);
|
|
intersection->x2 = rtengine::min(x2, other.x2);
|
|
intersection->y1 = rtengine::max(y1, other.y1);
|
|
intersection->y2 = rtengine::min(y2, other.y2);
|
|
|
|
if (intersection->x1 > intersection->x2 || intersection->y1 > intersection->y2) {
|
|
intersection.release();
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
if (intersection) {
|
|
intersection.release();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
Rectangle& operator+=(const Coord &v) {
|
|
x1 += v.x;
|
|
y1 += v.y;
|
|
x2 += v.x;
|
|
y2 += v.y;
|
|
return *this;
|
|
}
|
|
|
|
Rectangle& operator-=(const Coord &v) {
|
|
x1 -= v.x;
|
|
y1 -= v.y;
|
|
x2 -= v.x;
|
|
y2 -= v.y;
|
|
return *this;
|
|
}
|
|
|
|
Rectangle& operator/=(const int &v) {
|
|
if (v == 1) {
|
|
return *this;
|
|
}
|
|
|
|
int w = x2 - x1 + 1;
|
|
int h = y2 - y1 + 1;
|
|
w = w / v + (w % v > 0);
|
|
h = h / v + (h % v > 0);
|
|
x1 /= v;
|
|
y1 /= v;
|
|
x2 = x1 + w - 1;
|
|
y2 = y1 + h - 1;
|
|
|
|
return *this;
|
|
}
|
|
};
|
|
|
|
private:
|
|
Type type;
|
|
Imagefloat* image;
|
|
|
|
public:
|
|
// top/left and bottom/right coordinates of the spot in image space (at some point divided by scale factor)
|
|
Rectangle spotArea;
|
|
// top/left and bottom/right coordinates of the spot in scaled image space (on borders, imgArea won't cover spotArea)
|
|
Rectangle imgArea;
|
|
// top/left and bottom/right coordinates of useful part of the image in scaled image space (rounding error workaround)
|
|
Rectangle intersectionArea;
|
|
float radius;
|
|
float featherRadius;
|
|
|
|
SpotBox (int tl_x, int tl_y, int br_x, int br_y, int radius, int feather_radius, Imagefloat* image, Type type) :
|
|
type(type),
|
|
image(image),
|
|
spotArea(tl_x, tl_y, br_x, br_y),
|
|
imgArea(spotArea),
|
|
intersectionArea(),
|
|
radius(radius),
|
|
featherRadius(feather_radius)
|
|
{}
|
|
|
|
SpotBox (int tl_x, int tl_y, int radius, int feather_radius, Imagefloat* image, Type type) :
|
|
type(type),
|
|
image(image),
|
|
spotArea(tl_x, tl_y, image ? tl_x + image->getWidth() - 1 : 0, image ? tl_y + image->getHeight() - 1 : 0),
|
|
imgArea(spotArea),
|
|
intersectionArea(),
|
|
radius(radius),
|
|
featherRadius(feather_radius)
|
|
{}
|
|
|
|
SpotBox (SpotEntry &spot, Type type) :
|
|
type(type),
|
|
image(nullptr),
|
|
intersectionArea(),
|
|
radius(spot.radius),
|
|
featherRadius(int(spot.getFeatherRadius() + 0.5f)) // rounding to int before resizing
|
|
{
|
|
spotArea.x1 = int ((type == Type::SOURCE ? spot.sourcePos.x : spot.targetPos.x) - featherRadius);
|
|
spotArea.x2 = int ((type == Type::SOURCE ? spot.sourcePos.x : spot.targetPos.x) + featherRadius);
|
|
spotArea.y1 = int ((type == Type::SOURCE ? spot.sourcePos.y : spot.targetPos.y) - featherRadius);
|
|
spotArea.y2 = int ((type == Type::SOURCE ? spot.sourcePos.y : spot.targetPos.y) + featherRadius);
|
|
imgArea = spotArea;
|
|
}
|
|
|
|
~SpotBox() {
|
|
if (image && type != Type::FINAL) {
|
|
delete image;
|
|
}
|
|
}
|
|
|
|
SpotBox& operator /=(const int& v) {
|
|
if (v == 1) {
|
|
return *this;
|
|
}
|
|
spotArea /= v;
|
|
imgArea /= v;
|
|
radius /= float(v);
|
|
featherRadius = getWidth() / 2.f;
|
|
// intersectionArea doesn't need resize, because it's set after resizing
|
|
return *this;
|
|
}
|
|
|
|
int getWidth() {
|
|
return spotArea.x2 - spotArea.x1 + 1;
|
|
}
|
|
|
|
int getHeight() {
|
|
return spotArea.y2 - spotArea.y1 + 1;
|
|
}
|
|
|
|
int getImageWidth() {
|
|
return imgArea.x2 - imgArea.x1 + 1;
|
|
}
|
|
|
|
int getImageHeight() {
|
|
return imgArea.y2 - imgArea.y1 + 1;
|
|
}
|
|
|
|
int getIntersectionWidth() {
|
|
return intersectionArea.x2 - intersectionArea.x1 + 1;
|
|
}
|
|
|
|
int getIntersectionHeight() {
|
|
return intersectionArea.y2 - intersectionArea.y1 + 1;
|
|
}
|
|
|
|
bool checkImageSize() {
|
|
if (!image || getImageWidth() != image->getWidth() || getImageHeight() != image->getHeight()) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void tuneImageSize() {
|
|
if (!image) {
|
|
return;
|
|
}
|
|
if (getImageWidth() > image->getWidth()) {
|
|
imgArea.x2 = imgArea.x1 + image->getWidth() - 1;
|
|
}
|
|
if (getImageHeight() > image->getHeight()) {
|
|
imgArea.y2 = imgArea.y1 + image->getHeight() - 1;
|
|
}
|
|
}
|
|
|
|
Imagefloat *getImage() { // TODO: this should send back a const value, but getImage don't want it to be const...
|
|
return image;
|
|
}
|
|
|
|
void allocImage() {
|
|
int newW = imgArea.x2 - imgArea.x1 + 1;
|
|
int newH = imgArea.y2 - imgArea.y1 + 1;
|
|
|
|
if (image && type != Type::FINAL && (image->getWidth() != newW || image->getHeight() != newH)) {
|
|
delete image;
|
|
image = nullptr;
|
|
}
|
|
if (image == nullptr) {
|
|
image = new Imagefloat(newW, newH);
|
|
}
|
|
}
|
|
|
|
bool spotIntersects(const SpotBox &other) const {
|
|
return spotArea.intersects(other.spotArea);
|
|
}
|
|
|
|
bool getSpotIntersection(const SpotBox &other, std::unique_ptr<Rectangle> &intersection) const {
|
|
return spotArea.getIntersection(other.spotArea, intersection);
|
|
}
|
|
|
|
bool imageIntersects(const SpotBox &other, bool atDestLocation=false) const {
|
|
if (atDestLocation) {
|
|
Coord v(other.spotArea.x1 - spotArea.x1, other.spotArea.y1 - spotArea.y1);
|
|
Rectangle imgArea2(imgArea.x1, imgArea.y1, imgArea.x2, imgArea.y2);
|
|
imgArea2 += v;
|
|
return imgArea2.intersects(other.imgArea);
|
|
}
|
|
return imgArea.intersects(other.imgArea);
|
|
}
|
|
|
|
bool mutuallyClipImageArea(SpotBox &other) {
|
|
Coord v(other.spotArea.x1 - spotArea.x1, other.spotArea.y1 - spotArea.y1);
|
|
Rectangle imgArea2 = imgArea;
|
|
imgArea2 += v;
|
|
std::unique_ptr<Rectangle> intersection;
|
|
if (!imgArea2.getIntersection(other.imgArea, intersection)) {
|
|
return false;
|
|
}
|
|
other.intersectionArea = *intersection;
|
|
Coord v2(-v.x, -v.y);
|
|
*intersection -= v;
|
|
intersectionArea = *intersection;
|
|
return true;
|
|
}
|
|
|
|
bool setIntersectionWith(const SpotBox &other) {
|
|
if (!spotIntersects(other)) {
|
|
return false;
|
|
}
|
|
imgArea.x1 = rtengine::max(spotArea.x1, other.spotArea.x1);
|
|
imgArea.x2 = rtengine::min(spotArea.x2, other.spotArea.x2);
|
|
imgArea.y1 = rtengine::max(spotArea.y1, other.spotArea.y1);
|
|
imgArea.y2 = rtengine::min(spotArea.y2, other.spotArea.y2);
|
|
if (imgArea.x1 > imgArea.x2 || imgArea.y1 > imgArea.y2) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
#define ALGO 1
|
|
|
|
#if ALGO==1
|
|
bool processIntersectionWith(SpotBox &destBox) {
|
|
Imagefloat *dstImg = destBox.image;
|
|
|
|
if (image == nullptr || dstImg == nullptr) {
|
|
std::cerr << "One of the source or destination SpotBox image is missing !" << std::endl;
|
|
return false;
|
|
}
|
|
|
|
int srcImgY = intersectionArea.y1 - imgArea.y1;
|
|
int dstImgY = destBox.intersectionArea.y1 - destBox.imgArea.y1;
|
|
for (int y = intersectionArea.y1; y <= intersectionArea.y2; ++y) {
|
|
float dy = float(y - spotArea.y1) - featherRadius;
|
|
|
|
int srcImgX = intersectionArea.x1 - imgArea.x1;
|
|
int dstImgX = destBox.intersectionArea.x1 - destBox.imgArea.x1;
|
|
for (int x = intersectionArea.x1; x <= intersectionArea.x2; ++x) {
|
|
float dx = float(x - spotArea.x1) - featherRadius;
|
|
float r = sqrt(dx * dx + dy * dy);
|
|
|
|
if (r >= featherRadius) {
|
|
++srcImgX;
|
|
++dstImgX;
|
|
continue;
|
|
}
|
|
if (r <= radius) {
|
|
dstImg->r(dstImgY, dstImgX) = image->r(srcImgY, srcImgX);
|
|
dstImg->g(dstImgY, dstImgX) = image->g(srcImgY, srcImgX);
|
|
dstImg->b(dstImgY, dstImgX) = image->b(srcImgY, srcImgX);
|
|
} else {
|
|
float opacity = (featherRadius - r) / (featherRadius - radius);
|
|
dstImg->r(dstImgY, dstImgX) = (image->r(srcImgY, srcImgX) - dstImg->r(dstImgY, dstImgX)) * opacity + dstImg->r(dstImgY,dstImgX);
|
|
dstImg->g(dstImgY, dstImgX) = (image->g(srcImgY, srcImgX) - dstImg->g(dstImgY, dstImgX)) * opacity + dstImg->g(dstImgY,dstImgX);
|
|
dstImg->b(dstImgY, dstImgX) = (image->b(srcImgY, srcImgX) - dstImg->b(dstImgY, dstImgX)) * opacity + dstImg->b(dstImgY,dstImgX);
|
|
}
|
|
++srcImgX;
|
|
++dstImgX;
|
|
}
|
|
++srcImgY;
|
|
++dstImgY;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
#if ALGO==2
|
|
bool processIntersectionWith(SpotBox &destBox) {
|
|
/* The following disabled code has been taken from Gimp 2.8.10 and converted
|
|
* for RawTherapee by Jean-Christophe FRISCH (aka Hombre) on 02.19.2014.
|
|
* It has not been tested, at all, an can (does) contain bugs. Feel free to
|
|
* replace the actual working code by this one, if results are better.
|
|
*/
|
|
|
|
|
|
/* ORIGINAL NOTES
|
|
*
|
|
* The method used here is similar to the lighting invariant correction
|
|
* method but slightly different: we do not divide the RGB components,
|
|
* but substract them I2 = I0 - I1, where I0 is the sample image to be
|
|
* corrected, I1 is the reference pattern. Then we solve DeltaI=0
|
|
* (Laplace) with I2 Dirichlet conditions at the borders of the
|
|
* mask. The solver is a unoptimized red/black checker Gauss-Siedel
|
|
* with an over-relaxation factor of 1.8. It can benefit from a
|
|
* multi-grid evaluation of an initial solution before the main
|
|
* iteration loop.
|
|
*
|
|
* I reduced the convergence criteria to 0.1% (0.001) as we are
|
|
* dealing here with RGB integer components, more is overkill.
|
|
*
|
|
* Jean-Yves Couleaud cjyves@free.fr
|
|
*/
|
|
|
|
|
|
/* Original Algorithm Design:
|
|
*
|
|
* T. Georgiev, "Photoshop Healing Brush: a Tool for Seamless Cloning
|
|
* http://www.tgeorgiev.net/Photoshop_Healing.pdf
|
|
*/
|
|
|
|
// ----------------- Core function -----------------
|
|
|
|
int scaledPPX = pp.getX() / pp.skip;
|
|
int scaledPPY = pp.getY() / pp.skip;
|
|
int scaledPPW = pp.getWidth() / pp.skip + (pp.getWidth() % pp.getSkip() > 0);
|
|
int scaledPPH = pp.getHeight() / pp.skip + (pp.getHeight() % pp.skip > 0);
|
|
|
|
int sizeX = dst_XMax - dst_XMin + 1;
|
|
int sizeY = dst_YMax - dst_YMin + 1;
|
|
|
|
Imagefloat matrix (sizeX, sizeY);
|
|
Imagefloat solution (sizeX, sizeY);
|
|
|
|
// allocate the mask and draw it
|
|
mask.setSize (sizeX, sizeY);
|
|
{
|
|
Cairo::RefPtr<Cairo::Context> cr = Cairo::Context::create (mask.getSurface());
|
|
|
|
// clear the bitmap
|
|
cr->set_source_rgba (0., 0., 0., 0.);
|
|
cr->rectangle (0., 0., sizeX, sizeY);
|
|
cr->set_line_width (0.);
|
|
cr->fill();
|
|
|
|
// draw the mask
|
|
cr->set_antialias (Cairo::ANTIALIAS_GRAY);
|
|
cr->set_line_width (featherRadius);
|
|
double gradientCenterX = double (sizeX) / 2.;
|
|
double gradientCenterY = double (sizeY) / 2.;
|
|
{
|
|
Cairo::RefPtr<Cairo::RadialGradient> radialGradient = Cairo::RadialGradient::create (
|
|
gradientCenterX, gradientCenterY, radius,
|
|
gradientCenterX, gradientCenterY, featherRadius
|
|
);
|
|
radialGradient->add_color_stop_rgb (0., 0., 0., 1.);
|
|
radialGradient->add_color_stop_rgb (1., 0., 0., 0.);
|
|
cr->set_source_rgba (0., 0., 0., 1.);
|
|
cr->mask (radialGradient);
|
|
cr->rectangle (0., 0., sizeX, sizeY);
|
|
cr->fill();
|
|
}
|
|
}
|
|
|
|
// copy the src part to a temporary buffer to avoid possible self modified source
|
|
Imagefloat *srcBuff = img->copySubRegion (srcX, srcY, sizeX, sizeY);
|
|
|
|
|
|
// subtract pattern to image and store the result as a double in matrix
|
|
for (int i = 0, i2 = dst_YMin; i2 < sizeY - 1; ++i, ++i2) {
|
|
for (int j = 0, j2 = dst_XMin; i2 < sizeX - 1; ++j, ++j2) {
|
|
matrix.r (i, j) = img->r (i2, j2) - srcBuff->r (i, j);
|
|
matrix.g (i, j) = img->g (i2, j2) - srcBuff->g (i, j);
|
|
matrix.b (i, j) = img->b (i2, j2) - srcBuff->b (i, j);
|
|
}
|
|
}
|
|
|
|
|
|
// FIXME: is a faster implementation needed?
|
|
#define EPSILON 0.001
|
|
#define MAX_ITER 500
|
|
|
|
// repeat until convergence or max iterations
|
|
for (int n = 0; n < MAX_ITER; ++n) {
|
|
|
|
// ----------------------------------------------------------------
|
|
|
|
/* Perform one iteration of the Laplace solver for matrix. Store the
|
|
* result in solution and get the square of the cumulative error
|
|
* of the solution.
|
|
*/
|
|
int i, j;
|
|
double tmp, diff;
|
|
double sqr_err_r = 0.0;
|
|
double sqr_err_g = 0.0;
|
|
double sqr_err_b = 0.0;
|
|
const double w = 1.80 * 0.25; /* Over-relaxation = 1.8 */
|
|
|
|
// we use a red/black checker model of the discretization grid
|
|
|
|
// do reds
|
|
for (i = 0; i < matrix.getHeight(); ++i) {
|
|
for (j = i % 2; j < matrix.getWidth(); j += 2) {
|
|
if ((0 == mask (i, j)) || (i == 0) || (i == (matrix.getHeight() - 1)) || (j == 0) || (j == (matrix.getWidth() - 1))) {
|
|
// do nothing at the boundary or outside mask
|
|
solution.r (i, j) = matrix.r (i, j);
|
|
solution.g (i, j) = matrix.g (i, j);
|
|
solution.b (i, j) = matrix.b (i, j);
|
|
} else {
|
|
// Use Gauss Siedel to get the correction factor then over-relax it
|
|
tmp = solution.r (i, j);
|
|
solution.r (i, j) = (matrix.r (i, j) + w *
|
|
(
|
|
matrix.r (i, j - 1) + // west
|
|
matrix.r (i, j + 1) + // east
|
|
matrix.r (i - 1, j) + // north
|
|
matrix.r (i + 1, j) - 4.0 * matrix.r (i, j) // south
|
|
)
|
|
);
|
|
|
|
diff = solution.r (i, j) - tmp;
|
|
sqr_err_r += diff * diff;
|
|
|
|
|
|
tmp = solution.g (i, j);
|
|
solution.g (i, j) = (matrix.g (i, j) + w *
|
|
(
|
|
matrix.g (i, j - 1) + // west
|
|
matrix.g (i, j + 1) + // east
|
|
matrix.g (i - 1, j) + // north
|
|
matrix.g (i + 1, j) - 4.0 * matrix.g (i, j) // south
|
|
)
|
|
);
|
|
|
|
diff = solution.g (i, j) - tmp;
|
|
sqr_err_g += diff * diff;
|
|
|
|
|
|
|
|
tmp = solution.b (i, j);
|
|
solution.b (i, j) = (matrix.b (i, j) + w *
|
|
(
|
|
matrix.b (i, j - 1) + // west
|
|
matrix.b (i, j + 1) + // east
|
|
matrix.b (i - 1, j) + // north
|
|
matrix.b (i + 1, j) - 4.0 * matrix.b (i, j) // south
|
|
)
|
|
);
|
|
|
|
diff = solution.b (i, j) - tmp;
|
|
sqr_err_b += diff * diff;
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/* Do blacks
|
|
*
|
|
* As we've done the reds earlier, we can use them right now to
|
|
* accelerate the convergence. So we have "solution" in the solver
|
|
* instead of "matrix" above
|
|
*/
|
|
for (i = 0; i < matrix.getHeight(); i++) {
|
|
for (j = (i % 2) ? 0 : 1; j < matrix.getWidth(); j += 2) {
|
|
if ((0 == mask (i, j)) || (i == 0) || (i == (matrix.getHeight() - 1)) || (j == 0) || (j == (matrix.getWidth() - 1))) {
|
|
// do nothing at the boundary or outside mask
|
|
solution.r (i, j) = matrix.r (i, j);
|
|
solution.g (i, j) = matrix.g (i, j);
|
|
solution.b (i, j) = matrix.b (i, j);
|
|
} else {
|
|
// Use Gauss Siedel to get the correction factor then over-relax it
|
|
tmp = solution.r (i, j);
|
|
solution.r (i, j) = (matrix.r (i, j) + w *
|
|
(
|
|
matrix.r (i, j - 1) + // west
|
|
matrix.r (i, j + 1) + // east
|
|
matrix.r (i - 1, j) + // north
|
|
matrix.r (i + 1, j) - 4.0 * matrix.r (i, j) // south
|
|
)
|
|
);
|
|
|
|
diff = solution.r (i, j) - tmp;
|
|
sqr_err_r += diff * diff;
|
|
|
|
|
|
|
|
tmp = solution.g (i, j);
|
|
solution.g (i, j) = (matrix.g (i, j) + w *
|
|
(
|
|
matrix.g (i, j - 1) + // west
|
|
matrix.g (i, j + 1) + // east
|
|
matrix.g (i - 1, j) + // north
|
|
matrix.g (i + 1, j) - 4.0 * matrix.g (i, j) // south
|
|
)
|
|
);
|
|
|
|
diff = solution.g (i, j) - tmp;
|
|
sqr_err_g += diff * diff;
|
|
|
|
|
|
|
|
tmp = solution.b (i, j);
|
|
solution.b (i, j) = (matrix.b (i, j) + w *
|
|
(
|
|
matrix.b (i, j - 1) + // west
|
|
matrix.b (i, j + 1) + // east
|
|
matrix.b (i - 1, j) + // north
|
|
matrix.b (i + 1, j) - 4.0 * matrix.b (i, j) // south
|
|
)
|
|
);
|
|
|
|
diff = solution.b (i, j) - tmp;
|
|
sqr_err_b += diff * diff;
|
|
}
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------------
|
|
|
|
// copy solution to matrix
|
|
solution.copyData (&matrix);
|
|
|
|
if (sqr_err_r < EPSILON && sqr_err_g < EPSILON && sqr_err_b < EPSILON) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// add solution to original image and store in tempPR
|
|
for (int i = 0, i2 = dst_YMin; i2 < dst_YMax - 1; ++i, ++i2) {
|
|
if (i2 < 0 || i2 >= img->getHeight()) {
|
|
continue;
|
|
}
|
|
|
|
for (int j = 0, j2 = dst_XMin; j2 < dst_XMax - 1; ++j, ++j2) {
|
|
if (j2 < 0 || j2 >= img->getWidth()) {
|
|
continue;
|
|
}
|
|
|
|
float c2 = float (mask (i, j)) / 255.f;
|
|
float c1 = 1.f - c2;
|
|
resultPR->r(i,j) = (unsigned char) CLAMP0255 ( ROUND( double(first->r(i,j)) + double(secondPR->r(i,j)) ) );
|
|
img->r(i2,j2) = img->r(i2,j2)*c1 + (solution.r(i,j) + srcBuff->r(i,j))*c2;
|
|
img->g(i2,j2) = img->g(i2,j2)*c1 + (solution.g(i,j) + srcBuff->g(i,j))*c2;
|
|
img->b(i2,j2) = img->b(i2,j2)*c1 + (solution.b(i,j) + srcBuff->b(i,j))*c2;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// Copy the intersecting part
|
|
bool copyImgTo(SpotBox &destBox) {
|
|
Imagefloat *destImg = destBox.image;
|
|
|
|
if (image == nullptr || destImg == nullptr) {
|
|
std::cerr << "One of the source or destination SpotBox image is missing !" << std::endl;
|
|
return false;
|
|
}
|
|
|
|
std::unique_ptr<Rectangle> intersection;
|
|
|
|
if (!intersectionArea.getIntersection(destBox.intersectionArea, intersection)) {
|
|
return false;
|
|
}
|
|
|
|
Imagefloat *srcImg = image;
|
|
Imagefloat *dstImg = destBox.image;
|
|
|
|
int srcImgY = intersection->y1 - imgArea.y1;
|
|
int dstImgY = intersection->y1 - destBox.imgArea.y1;
|
|
for (int y = intersection->y1; y <= intersection->y2; ++y) {
|
|
int srcImgX = intersection->x1 - imgArea.x1;
|
|
int dstImgX = intersection->x1 - destBox.imgArea.x1;
|
|
|
|
for (int x = intersection->x1; x <= intersection->x2; ++x) {
|
|
dstImg->r(dstImgY, dstImgX) = srcImg->r(srcImgY, srcImgX);
|
|
dstImg->g(dstImgY, dstImgX) = srcImg->g(srcImgY, srcImgX);
|
|
dstImg->b(dstImgY, dstImgX) = srcImg->b(srcImgY, srcImgX);
|
|
++srcImgX;
|
|
++dstImgX;
|
|
}
|
|
++srcImgY;
|
|
++dstImgY;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
};
|
|
|
|
void ImProcFunctions::removeSpots (Imagefloat* img, ImageSource* imgsrc, const std::vector<SpotEntry> &entries, const PreviewProps &pp, const ColorTemp &currWB, const ColorManagementParams *cmp, int tr)
|
|
{
|
|
//Get the clipped image areas (src & dst) from the source image
|
|
|
|
std::vector< std::shared_ptr<SpotBox> > srcSpotBoxs;
|
|
std::vector< std::shared_ptr<SpotBox> > dstSpotBoxs;
|
|
int fullImgWidth = 0;
|
|
int fullImgHeight = 0;
|
|
imgsrc->getFullSize(fullImgWidth, fullImgHeight, tr);
|
|
SpotBox fullImageBox(0, 0, fullImgWidth - 1, fullImgHeight - 1, 0, 0, nullptr, SpotBox::Type::FINAL);
|
|
SpotBox cropBox(pp.getX(), pp.getY(),
|
|
pp.getX() + pp.getWidth() - 1, pp.getY() + pp.getHeight() - 1,
|
|
0, 0, img, SpotBox::Type::FINAL);
|
|
|
|
std::set<int> visibleSpots; // list of dest spots intersecting the preview's crop
|
|
int i = 0;
|
|
|
|
for (auto entry : params->spot.entries) {
|
|
std::shared_ptr<SpotBox> srcSpotBox(new SpotBox(entry, SpotBox::Type::SOURCE));
|
|
std::shared_ptr<SpotBox> dstSpotBox(new SpotBox(entry, SpotBox::Type::TARGET));
|
|
if ( !srcSpotBox->setIntersectionWith(fullImageBox)
|
|
|| !dstSpotBox->setIntersectionWith(fullImageBox)
|
|
|| !srcSpotBox->imageIntersects(*dstSpotBox, true))
|
|
{
|
|
continue;
|
|
++i;
|
|
}
|
|
|
|
// If spot intersect the preview image, add it to the visible spots
|
|
if (dstSpotBox->spotIntersects(cropBox)) {
|
|
visibleSpots.insert(i);
|
|
}
|
|
++i;
|
|
|
|
// Source area
|
|
PreviewProps spp(srcSpotBox->imgArea.x1, srcSpotBox->imgArea.y1,
|
|
srcSpotBox->getImageWidth(), srcSpotBox->getImageHeight(), pp.getSkip());
|
|
int w = 0;
|
|
int h = 0;
|
|
imgsrc->getSize(spp, w, h);
|
|
*srcSpotBox /= pp.getSkip();
|
|
srcSpotBox->allocImage();
|
|
Imagefloat *srcImage = srcSpotBox->getImage();
|
|
for (int y = 0; y < (int)srcImage->getHeight(); ++y) {
|
|
for (int x = 0; x < (int)srcImage->getWidth(); ++x) {
|
|
srcImage->r(y, x) = 60000.f;
|
|
srcImage->g(y, x) = 500.f;
|
|
srcImage->b(y, x) = 500.f;
|
|
}
|
|
}
|
|
|
|
imgsrc->getImage(currWB, tr, srcSpotBox->getImage(), spp, params->toneCurve, params->raw);
|
|
if (cmp) {
|
|
imgsrc->convertColorSpace(srcImage, *cmp, currWB);
|
|
}
|
|
assert(srcSpotBox->checkImageSize());
|
|
|
|
|
|
// Destination area
|
|
spp.set(dstSpotBox->imgArea.x1, dstSpotBox->imgArea.y1, dstSpotBox->getImageWidth(),
|
|
dstSpotBox->getImageHeight(), pp.getSkip());
|
|
*dstSpotBox /= pp.getSkip();
|
|
dstSpotBox->allocImage();
|
|
Imagefloat *dstImage = dstSpotBox->getImage();
|
|
for (int y = 0; y < (int)dstImage->getHeight(); ++y) {
|
|
for (int x = 0; x < (int)dstImage->getWidth(); ++x) {
|
|
dstImage->r(y, x) = 500.f;
|
|
dstImage->g(y, x) = 500.f;
|
|
dstImage->b(y, x) = 60000.f;
|
|
}
|
|
}
|
|
imgsrc->getImage(currWB, tr, dstSpotBox->getImage(), spp, params->toneCurve, params->raw);
|
|
if (cmp) {
|
|
imgsrc->convertColorSpace(dstImage, *cmp, currWB);
|
|
}
|
|
assert(dstSpotBox->checkImageSize());
|
|
|
|
// Update the intersectionArea between src and dest
|
|
if (srcSpotBox->mutuallyClipImageArea(*dstSpotBox)) {
|
|
srcSpotBoxs.push_back(srcSpotBox);
|
|
dstSpotBoxs.push_back(dstSpotBox);
|
|
}
|
|
|
|
}
|
|
|
|
// Construct list of upstream dependancies
|
|
|
|
std::set<int> requiredSpots = visibleSpots; // starting point, visible spots are necessarilly required spots
|
|
for (auto i = requiredSpots.rbegin(); i != requiredSpots.rend(); i++) {
|
|
int spotNbr = *i;
|
|
requiredSpots.insert(spotNbr);
|
|
if (spotNbr > 0) {
|
|
for (int j = spotNbr - 1; j >= 0; --j) {
|
|
if ((srcSpotBoxs.at(spotNbr))->imageIntersects(*dstSpotBoxs.at(j))) {
|
|
requiredSpots.insert(spotNbr);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Process spots and copy them downstream
|
|
|
|
for (auto i = requiredSpots.begin(); i != requiredSpots.end(); i++) {
|
|
// Process
|
|
srcSpotBoxs.at(*i)->processIntersectionWith(*dstSpotBoxs.at(*i));
|
|
|
|
// Propagate
|
|
std::set<int> positiveSpots; // For DEBUG purpose only !
|
|
auto j = i;
|
|
++j;
|
|
while (j != requiredSpots.end()) {
|
|
bool intersectionFound = false;
|
|
int i_ = *i;
|
|
int j_ = *j;
|
|
intersectionFound |= dstSpotBoxs.at(i_)->copyImgTo(*srcSpotBoxs.at(j_));
|
|
intersectionFound |= dstSpotBoxs.at(i_)->copyImgTo(*dstSpotBoxs.at(j_));
|
|
if (intersectionFound) {
|
|
positiveSpots.insert(j_);
|
|
}
|
|
++j;
|
|
}
|
|
}
|
|
|
|
// Copy the dest spot to the preview image
|
|
cropBox /= pp.getSkip();
|
|
cropBox.tuneImageSize();
|
|
cropBox.intersectionArea = cropBox.imgArea;
|
|
|
|
int f = 0;
|
|
for (auto i : visibleSpots) {
|
|
f += dstSpotBoxs.at(i)->copyImgTo(cropBox) ? 1 : 0;
|
|
}
|
|
}
|
|
|
|
}
|
|
|