Revise perspective transformation to remove hard-coded angular field of view and horizontal perspective axis of rotation. Add vertical bias parameter to retain ability to perform vertical perspective transformation independent of the horizontal perspective axis of rotation. Add field of view parameter as a tentative method for specifying angular field of view. The current implementation of perspective transformation applies horizontal perspective transformation in such a way that preserves the orientation of a horizontal line going through the center of the image. In common use cases, horizontal lines such as the horizon do not go through the center of the image. In such cases, the horizontal perspective axis of rotation should not be parallel to the image's y-axis. This commit makes the axis of rotation dependent on the vertical parameter. The two axes of rotation should be placed at the appropriate distance from the image in order to prevent stretched or compressed proportions. In the current implementation, the axes are at a fixed relative distance from the image. This commit adds the ability to specify the distance in the form of the diagonal angular field of view.
1221 lines
43 KiB
C++
1221 lines
43 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 <https://www.gnu.org/licenses/>.
|
|
*/
|
|
#include <array>
|
|
|
|
#include "imagefloat.h"
|
|
#include "improcfun.h"
|
|
|
|
#include "procparams.h"
|
|
#include "rt_math.h"
|
|
#include "rtengine.h"
|
|
#include "rtlensfun.h"
|
|
#include "sleef.h"
|
|
|
|
using namespace std;
|
|
|
|
namespace
|
|
{
|
|
|
|
float pow3 (float x)
|
|
{
|
|
return x * x * x;
|
|
}
|
|
|
|
float pow4 (float x)
|
|
{
|
|
return (x * x) * (x * x);
|
|
}
|
|
|
|
float pown (float x, int n)
|
|
{
|
|
|
|
switch (n) {
|
|
case 0:
|
|
return 1;
|
|
|
|
case 2:
|
|
return x * x;
|
|
|
|
case 4:
|
|
return pow4 (x);
|
|
|
|
case 6:
|
|
return (x * x) * pow4 (x);
|
|
|
|
case 8:
|
|
return pow4 (x) * pow4 (x);
|
|
|
|
default:
|
|
return pow_F (x, n);
|
|
}
|
|
}
|
|
|
|
float normn (float a, float b, int n)
|
|
{
|
|
switch (n) {
|
|
case 2:
|
|
return sqrtf (a * a + b * b);
|
|
|
|
case 4:
|
|
return sqrtf (sqrtf (pow4 (a) + pow4 (b)));
|
|
|
|
case 6:
|
|
return sqrtf (xcbrtf (pow3 (a) * pow3 (a) + pow3 (b) * pow3 (b)));
|
|
|
|
case 8:
|
|
return sqrtf (sqrtf (sqrtf (pow4 (a) * pow4 (a) + pow4 (b) * pow4 (b))));
|
|
|
|
default:
|
|
return pow_F (pown (a, n) + pown (b, n), 1.f / n);
|
|
}
|
|
}
|
|
|
|
#ifdef __SSE2__
|
|
inline void interpolateTransformCubic(rtengine::Imagefloat* src, int xs, int ys, float Dx, float Dy, float &r, float &g, float &b, float mul)
|
|
{
|
|
constexpr float A = -0.85f;
|
|
|
|
// Vertical
|
|
const float t1Vert = A * (Dy - Dy * Dy);
|
|
const float t2Vert = (3.f - 2.f * Dy) * Dy * Dy;
|
|
const vfloat w3Vert = F2V(t1Vert * Dy);
|
|
const vfloat w2Vert = F2V(t1Vert * Dy - t1Vert + t2Vert);
|
|
const vfloat w1Vert = F2V(1.f - (t1Vert * Dy) - t2Vert);
|
|
const vfloat w0Vert = F2V(t1Vert - (t1Vert * Dy));
|
|
|
|
const vfloat rv = (w0Vert * LVFU(src->r(ys, xs)) + w1Vert * LVFU(src->r(ys + 1, xs))) + (w2Vert * LVFU(src->r(ys + 2, xs)) + w3Vert * LVFU(src->r(ys + 3, xs)));
|
|
const vfloat gv = (w0Vert * LVFU(src->g(ys, xs)) + w1Vert * LVFU(src->g(ys + 1, xs))) + (w2Vert * LVFU(src->g(ys + 2, xs)) + w3Vert * LVFU(src->g(ys + 3, xs)));
|
|
const vfloat bv = (w0Vert * LVFU(src->b(ys, xs)) + w1Vert * LVFU(src->b(ys + 1, xs))) + (w2Vert * LVFU(src->b(ys + 2, xs)) + w3Vert * LVFU(src->b(ys + 3, xs)));
|
|
|
|
// Horizontal
|
|
const float t1Hor = A * (Dx - Dx * Dx);
|
|
const float t2Hor = (3.f - 2.f * Dx) * Dx * Dx;
|
|
const vfloat weight = _mm_set_ps(t1Hor * Dx, t1Hor * Dx - t1Hor + t2Hor, 1.f - (t1Hor * Dx) - t2Hor, t1Hor - (t1Hor * Dx)) * F2V(mul);
|
|
r = vhadd(weight * rv);
|
|
g = vhadd(weight * gv);
|
|
b = vhadd(weight * bv);
|
|
}
|
|
#else
|
|
inline void interpolateTransformCubic(rtengine::Imagefloat* src, int xs, int ys, float Dx, float Dy, float &r, float &g, float &b, float mul)
|
|
{
|
|
constexpr float A = -0.85f;
|
|
|
|
// Vertical
|
|
const float t1Vert = A * (Dy - Dy * Dy);
|
|
const float t2Vert = (3.f - 2.f * Dy) * Dy * Dy;
|
|
const float w3Vert = t1Vert * Dy;
|
|
const float w2Vert = t1Vert * Dy - t1Vert + t2Vert;
|
|
const float w1Vert = 1.f - (t1Vert * Dy) - t2Vert;
|
|
const float w0Vert = t1Vert - (t1Vert * Dy);
|
|
|
|
float rv[4], gv[4], bv[4];
|
|
for (int i = 0; i < 4; ++i) {
|
|
rv[i] = w0Vert * src->r(ys, xs + i) + w1Vert * src->r(ys + 1, xs + i) + w2Vert * src->r(ys + 2, xs + i) + w3Vert * src->r(ys + 3, xs + i);
|
|
gv[i] = w0Vert * src->g(ys, xs + i) + w1Vert * src->g(ys + 1, xs + i) + w2Vert * src->g(ys + 2, xs + i) + w3Vert * src->g(ys + 3, xs + i);
|
|
bv[i] = w0Vert * src->b(ys, xs + i) + w1Vert * src->b(ys + 1, xs + i) + w2Vert * src->b(ys + 2, xs + i) + w3Vert * src->b(ys + 3, xs + i);
|
|
}
|
|
|
|
// Horizontal
|
|
const float t1Hor = A * (Dx - Dx * Dx);
|
|
const float t2Hor = (3.f - 2.f * Dx) * Dx * Dx;
|
|
const float w3Hor = t1Hor * Dx;
|
|
const float w2Hor = t1Hor * Dx - t1Hor + t2Hor;
|
|
const float w1Hor = 1.f - (t1Hor * Dx) - t2Hor;
|
|
const float w0Hor = t1Hor - (t1Hor * Dx);
|
|
|
|
r = mul * (rv[0] * w0Hor + rv[1] * w1Hor + rv[2] * w2Hor + rv[3] * w3Hor);
|
|
g = mul * (gv[0] * w0Hor + gv[1] * w1Hor + gv[2] * w2Hor + gv[3] * w3Hor);
|
|
b = mul * (bv[0] * w0Hor + bv[1] * w1Hor + bv[2] * w2Hor + bv[3] * w3Hor);
|
|
}
|
|
#endif
|
|
#ifdef __SSE2__
|
|
inline void interpolateTransformChannelsCubic(const float* const* src, int xs, int ys, float Dx, float Dy, float& dest, float mul)
|
|
{
|
|
constexpr float A = -0.85f;
|
|
|
|
// Vertical
|
|
const float t1Vert = A * (Dy - Dy * Dy);
|
|
const float t2Vert = (3.f - 2.f * Dy) * Dy * Dy;
|
|
const vfloat w3Vert = F2V(t1Vert * Dy);
|
|
const vfloat w2Vert = F2V(t1Vert * Dy - t1Vert + t2Vert);
|
|
const vfloat w1Vert = F2V(1.f - (t1Vert * Dy) - t2Vert);
|
|
const vfloat w0Vert = F2V(t1Vert - (t1Vert * Dy));
|
|
|
|
const vfloat cv = (w0Vert * LVFU(src[ys][xs]) + w1Vert * LVFU(src[ys + 1][xs])) + (w2Vert * LVFU(src[ys + 2][xs]) + w3Vert * LVFU(src[ys + 3][xs]));
|
|
|
|
// Horizontal
|
|
const float t1Hor = A * (Dx - Dx * Dx);
|
|
const float t2Hor = (3.f - 2.f * Dx) * Dx * Dx;
|
|
const vfloat weight = _mm_set_ps(t1Hor * Dx, t1Hor * Dx - t1Hor + t2Hor, 1.f - (t1Hor * Dx) - t2Hor, t1Hor - (t1Hor * Dx));
|
|
dest = mul * vhadd(weight * cv);
|
|
}
|
|
#else
|
|
inline void interpolateTransformChannelsCubic(const float* const* src, int xs, int ys, float Dx, float Dy, float& dest, float mul)
|
|
{
|
|
constexpr float A = -0.85f;
|
|
|
|
// Vertical
|
|
const float t1Vert = A * (Dy - Dy * Dy);
|
|
const float t2Vert = (3.f - 2.f * Dy) * Dy * Dy;
|
|
const float w3Vert = t1Vert * Dy;
|
|
const float w2Vert = t1Vert * Dy - t1Vert + t2Vert;
|
|
const float w1Vert = 1.f - (t1Vert * Dy) - t2Vert;
|
|
const float w0Vert = t1Vert - (t1Vert * Dy);
|
|
|
|
float cv[4];
|
|
for (int i = 0; i < 4; ++i) {
|
|
cv[i] = w0Vert * src[ys][xs + i] + w1Vert * src[ys + 1][xs + i] + w2Vert * src[ys + 2][xs + i] + w3Vert * src[ys + 3][xs + i];
|
|
}
|
|
|
|
// Horizontal
|
|
const float t1Hor = A * (Dx - Dx * Dx);
|
|
const float t2Hor = (3.f - 2.f * Dx) * Dx * Dx;
|
|
const float w3Hor = t1Hor * Dx;
|
|
const float w2Hor = t1Hor * Dx - t1Hor + t2Hor;
|
|
const float w1Hor = 1.f - (t1Hor * Dx) - t2Hor;
|
|
const float w0Hor = t1Hor - (t1Hor * Dx);
|
|
|
|
dest = mul * (cv[0] * w0Hor + cv[1] * w1Hor + cv[2] * w2Hor + cv[3] * w3Hor);
|
|
}
|
|
#endif
|
|
|
|
}
|
|
|
|
namespace rtengine
|
|
{
|
|
#undef CLIPTOC
|
|
|
|
#define CLIPTOC(a,b,c,d) ((a)>=(b)?((a)<=(c)?(a):(d=true,(c))):(d=true,(b)))
|
|
|
|
bool ImProcFunctions::transCoord (int W, int H, const std::vector<Coord2D> &src, std::vector<Coord2D> &red, std::vector<Coord2D> &green, std::vector<Coord2D> &blue, double ascaleDef,
|
|
const LensCorrection *pLCPMap) const
|
|
{
|
|
|
|
bool clipped = false;
|
|
|
|
red.clear ();
|
|
green.clear ();
|
|
blue.clear ();
|
|
|
|
if (!needsCA() && !needsDistortion() && !needsRotation() && !needsPerspective() && (!params->lensProf.useDist || pLCPMap == nullptr)) {
|
|
for (size_t i = 0; i < src.size(); i++) {
|
|
red.push_back (Coord2D (src[i].x, src[i].y));
|
|
green.push_back (Coord2D (src[i].x, src[i].y));
|
|
blue.push_back (Coord2D (src[i].x, src[i].y));
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
double oW = W, oH = H;
|
|
double w2 = (double) oW / 2.0 - 0.5;
|
|
double h2 = (double) oH / 2.0 - 0.5;
|
|
double maxRadius = sqrt ( (double) ( oW * oW + oH * oH ) ) / 2;
|
|
|
|
// auxiliary variables for distortion correction
|
|
bool needsDist = needsDistortion(); // for performance
|
|
double distAmount = params->distortion.amount;
|
|
|
|
// auxiliary variables for rotation
|
|
double cost = cos (params->rotate.degree * rtengine::RT_PI / 180.0);
|
|
double sint = sin (params->rotate.degree * rtengine::RT_PI / 180.0);
|
|
|
|
double ascale = ascaleDef > 0 ? ascaleDef : (params->commonTrans.autofill ? getTransformAutoFill (oW, oH, pLCPMap) : 1.0);
|
|
|
|
// auxiliary variables for perspective correction
|
|
const double f = maxRadius / tan(params->perspective.fov / 360.0 * rtengine::RT_PI);
|
|
const double phtheta = params->perspective.horizontal / -180.0 * rtengine::RT_PI;
|
|
const double pvtheta = params->perspective.vertical / -180.0 * rtengine::RT_PI;
|
|
const double pbtheta = params->perspective.vBias / -180.0 * rtengine::RT_PI;
|
|
const double phcos = cos(phtheta);
|
|
const double pvcos = cos(pvtheta);
|
|
const double pbcos = cos(pbtheta);
|
|
const double phsin = sin(phtheta);
|
|
const double pvsin = sin(pvtheta);
|
|
const double pbsin = sin(pbtheta);
|
|
// Coordinates of distorted image center.
|
|
const double pxoffset = f * phsin * pvcos;
|
|
const double pyoffset = -f * (phcos * pvcos * pbsin + pvsin * pbcos);
|
|
const double pz = f * (phcos * pvcos * pbcos - pvsin * pbsin);
|
|
// Inverse transformation matrix.
|
|
const double p_xx = f * phcos;
|
|
const double p_xy = f * phsin * pbsin;
|
|
const double p_xz = f * phsin * pbcos;
|
|
const double p_yx = f * phsin * pvsin;
|
|
const double p_yy = f * (pvcos * pbcos - phcos * pvsin * pbsin);
|
|
const double p_yz = f * (-pvcos * pbsin - phcos * pvsin * pbcos);
|
|
const double p_zx = -phsin * pvcos;
|
|
const double p_zy = phcos * pvcos * pbsin + pvsin * pbcos;
|
|
const double p_zz = phcos * pvcos * pbcos - pvsin * pbsin;
|
|
// z is known, can calculate these in advance.
|
|
const double pz_xz = pz * p_xz;
|
|
const double pz_yz = pz * p_yz;
|
|
const double pz_zz = pz * p_zz;
|
|
|
|
for (size_t i = 0; i < src.size(); i++) {
|
|
double x_d = src[i].x, y_d = src[i].y;
|
|
|
|
if (pLCPMap && params->lensProf.useDist) {
|
|
pLCPMap->correctDistortion(x_d, y_d, 0, 0, ascale);
|
|
} else {
|
|
x_d *= ascale;
|
|
y_d *= ascale;
|
|
}
|
|
|
|
x_d += ascale * (0 - w2); // centering x coord & scale
|
|
y_d += ascale * (0 - h2); // centering y coord & scale
|
|
|
|
if (needsPerspective()) {
|
|
x_d -= pxoffset;
|
|
y_d -= pyoffset;
|
|
const double normalizer = p_zx * x_d + p_zy * y_d + pz_zz;
|
|
const double x_d_new = p_xx * x_d + p_xy * y_d + pz_xz;
|
|
y_d = p_yx * x_d + p_yy * y_d + pz_yz;
|
|
x_d = x_d_new / normalizer;
|
|
y_d /= normalizer;
|
|
}
|
|
|
|
// rotate
|
|
double Dx = x_d * cost - y_d * sint;
|
|
double Dy = x_d * sint + y_d * cost;
|
|
|
|
// distortion correction
|
|
double s = 1;
|
|
|
|
if (needsDist) {
|
|
double r = sqrt (Dx * Dx + Dy * Dy) / maxRadius; // sqrt is slow
|
|
s = 1.0 - distAmount + distAmount * r ;
|
|
}
|
|
|
|
// LCP CA is not reflected in preview (and very small), so don't add it here
|
|
|
|
red.push_back (Coord2D (Dx * (s + params->cacorrection.red) + w2, Dy * (s + params->cacorrection.red) + h2));
|
|
green.push_back (Coord2D (Dx * s + w2, Dy * s + h2));
|
|
blue.push_back (Coord2D (Dx * (s + params->cacorrection.blue) + w2, Dy * (s + params->cacorrection.blue) + h2));
|
|
}
|
|
|
|
// Clip all points and track if they were any corrections
|
|
for (size_t i = 0; i < src.size(); i++) {
|
|
red[i].x = CLIPTOC (red[i].x, 0, W - 1, clipped);
|
|
red[i].y = CLIPTOC (red[i].y, 0, H - 1, clipped);
|
|
green[i].x = CLIPTOC (green[i].x, 0, W - 1, clipped);
|
|
green[i].y = CLIPTOC (green[i].y, 0, H - 1, clipped);
|
|
blue[i].x = CLIPTOC (blue[i].x, 0, W - 1, clipped);
|
|
blue[i].y = CLIPTOC (blue[i].y, 0, H - 1, clipped);
|
|
}
|
|
|
|
return clipped;
|
|
}
|
|
|
|
// Transform all corners and critical sidelines of an image
|
|
bool ImProcFunctions::transCoord (int W, int H, int x, int y, int w, int h, int& xv, int& yv, int& wv, int& hv, double ascaleDef, const LensCorrection *pLCPMap) const
|
|
{
|
|
const int DivisionsPerBorder = 32;
|
|
|
|
int x1 = x, y1 = y;
|
|
int x2 = x1 + w - 1;
|
|
int y2 = y1 + h - 1;
|
|
|
|
// Build all edge points and half-way points
|
|
std::vector<Coord2D> corners (8);
|
|
corners[0].set (x1, y1);
|
|
corners[1].set (x1, y2);
|
|
corners[2].set (x2, y2);
|
|
corners[3].set (x2, y1);
|
|
corners[4].set ((x1 + x2) / 2, y1);
|
|
corners[5].set ((x1 + x2) / 2, y2);
|
|
corners[6].set (x1, (y1 + y2) / 2);
|
|
corners[7].set (x2, (y1 + y2) / 2);
|
|
|
|
// Add several steps inbetween
|
|
int xstep = (x2 - x1) / DivisionsPerBorder;
|
|
|
|
if (xstep < 1) {
|
|
xstep = 1;
|
|
}
|
|
|
|
for (int i = x1 + xstep; i <= x2 - xstep; i += xstep) {
|
|
corners.push_back (Coord2D (i, y1));
|
|
corners.push_back (Coord2D (i, y2));
|
|
}
|
|
|
|
int ystep = (y2 - y1) / DivisionsPerBorder;
|
|
|
|
if (ystep < 1) {
|
|
ystep = 1;
|
|
}
|
|
|
|
for (int i = y1 + ystep; i <= y2 - ystep; i += ystep) {
|
|
corners.push_back (Coord2D (x1, i));
|
|
corners.push_back (Coord2D (x2, i));
|
|
}
|
|
|
|
std::vector<Coord2D> r, g, b;
|
|
|
|
bool clipped = transCoord (W, H, corners, r, g, b, ascaleDef, pLCPMap);
|
|
|
|
// Merge all R G Bs into one X/Y pool
|
|
std::vector<Coord2D> transCorners;
|
|
transCorners.insert (transCorners.end(), r.begin(), r.end());
|
|
transCorners.insert (transCorners.end(), g.begin(), g.end());
|
|
transCorners.insert (transCorners.end(), b.begin(), b.end());
|
|
|
|
// find the min/max of all coordinates, so the borders
|
|
double x1d = transCorners[0].x;
|
|
|
|
for (size_t i = 1; i < transCorners.size(); i++)
|
|
if (transCorners[i].x < x1d) {
|
|
x1d = transCorners[i].x;
|
|
}
|
|
|
|
int x1v = (int) (x1d);
|
|
|
|
double y1d = transCorners[0].y;
|
|
|
|
for (size_t i = 1; i < transCorners.size(); i++)
|
|
if (transCorners[i].y < y1d) {
|
|
y1d = transCorners[i].y;
|
|
}
|
|
|
|
int y1v = (int) (y1d);
|
|
|
|
double x2d = transCorners[0].x;
|
|
|
|
for (size_t i = 1; i < transCorners.size(); i++)
|
|
if (transCorners[i].x > x2d) {
|
|
x2d = transCorners[i].x;
|
|
}
|
|
|
|
int x2v = (int)ceil (x2d);
|
|
|
|
double y2d = transCorners[0].y;
|
|
|
|
for (size_t i = 1; i < transCorners.size(); i++)
|
|
if (transCorners[i].y > y2d) {
|
|
y2d = transCorners[i].y;
|
|
}
|
|
|
|
int y2v = (int)ceil (y2d);
|
|
|
|
xv = x1v;
|
|
yv = y1v;
|
|
wv = x2v - x1v + 1;
|
|
hv = y2v - y1v + 1;
|
|
|
|
return clipped;
|
|
}
|
|
|
|
void ImProcFunctions::transform (Imagefloat* original, Imagefloat* transformed, int cx, int cy, int sx, int sy, int oW, int oH, int fW, int fH,
|
|
const FramesMetaData *metadata,
|
|
int rawRotationDeg, bool fullImage)
|
|
{
|
|
double focalLen = metadata->getFocalLen();
|
|
double focalLen35mm = metadata->getFocalLen35mm();
|
|
float focusDist = metadata->getFocusDist();
|
|
double fNumber = metadata->getFNumber();
|
|
|
|
std::unique_ptr<const LensCorrection> pLCPMap;
|
|
|
|
if (needsLensfun()) {
|
|
pLCPMap = LFDatabase::getInstance()->findModifier(params->lensProf, metadata, oW, oH, params->coarse, rawRotationDeg);
|
|
} else if (needsLCP()) { // don't check focal length to allow distortion correction for lenses without chip
|
|
const std::shared_ptr<LCPProfile> pLCPProf = LCPStore::getInstance()->getProfile (params->lensProf.lcpFile);
|
|
|
|
if (pLCPProf) {
|
|
pLCPMap.reset(
|
|
new LCPMapper (pLCPProf, focalLen, focalLen35mm,
|
|
focusDist, fNumber, false,
|
|
false,
|
|
oW, oH, params->coarse, rawRotationDeg
|
|
)
|
|
);
|
|
}
|
|
}
|
|
|
|
if (! (needsCA() || needsDistortion() || needsRotation() || needsPerspective() || needsLCP() || needsLensfun()) && (needsVignetting() || needsPCVignetting() || needsGradient())) {
|
|
transformLuminanceOnly (original, transformed, cx, cy, oW, oH, fW, fH);
|
|
} else {
|
|
bool highQuality;
|
|
std::unique_ptr<Imagefloat> tmpimg;
|
|
Imagefloat *dest = transformed;
|
|
if (!needsCA() && scale != 1) {
|
|
highQuality = false;
|
|
} else {
|
|
highQuality = true;
|
|
// agriggio: CA correction via the lens profile has to be
|
|
// performed before separately from the the other transformations
|
|
// (except for the coarse rotation/flipping). In order to not
|
|
// change the code too much, I simply introduced a new mode
|
|
// TRANSFORM_HIGH_QUALITY_CA, which applies *only* profile-based
|
|
// CA correction. So, the correction in this case occurs in two
|
|
// steps, using an intermediate temporary image. There's room for
|
|
// optimization of course...
|
|
if (pLCPMap && params->lensProf.useCA && pLCPMap->isCACorrectionAvailable()) {
|
|
tmpimg.reset(new Imagefloat(original->getWidth(), original->getHeight()));
|
|
dest = tmpimg.get();
|
|
}
|
|
}
|
|
transformGeneral(highQuality, original, dest, cx, cy, sx, sy, oW, oH, fW, fH, pLCPMap.get());
|
|
|
|
if (highQuality && dest != transformed) {
|
|
transformLCPCAOnly(dest, transformed, cx, cy, pLCPMap.get());
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// helper function
|
|
void ImProcFunctions::calcVignettingParams (int oW, int oH, const VignettingParams& vignetting, double &w2, double &h2, double& maxRadius, double &v, double &b, double &mul)
|
|
{
|
|
// vignette center is a point with coordinates between -1 and +1
|
|
double x = vignetting.centerX / 100.0;
|
|
double y = vignetting.centerY / 100.0;
|
|
|
|
// calculate vignette center in pixels
|
|
w2 = (double) oW / 2.0 - 0.5 + x * oW;
|
|
h2 = (double) oH / 2.0 - 0.5 + y * oH;
|
|
|
|
// max vignette radius in pixels
|
|
maxRadius = sqrt ( (double) ( oW * oW + oH * oH ) ) / 2.;
|
|
|
|
// vignette variables with applied strength
|
|
v = 1.0 + vignetting.strength * fabs (vignetting.amount) * 3.0 / 400.0;
|
|
b = 1.0 + vignetting.radius * 7.0 / 100.0;
|
|
mul = (1.0 - v) / tanh (b);
|
|
}
|
|
|
|
struct grad_params {
|
|
bool angle_is_zero, transpose, bright_top;
|
|
float ta, yc, xc;
|
|
float ys, ys_inv;
|
|
float scale, botmul, topmul;
|
|
float top_edge_0;
|
|
int h;
|
|
};
|
|
static void calcGradientParams (int oW, int oH, const GradientParams& gradient, struct grad_params& gp)
|
|
{
|
|
int w = oW;
|
|
int h = oH;
|
|
double gradient_stops = gradient.strength;
|
|
double gradient_span = gradient.feather / 100.0;
|
|
double gradient_center_x = gradient.centerX / 200.0 + 0.5;
|
|
double gradient_center_y = gradient.centerY / 200.0 + 0.5;
|
|
double gradient_angle = gradient.degree / 180.0 * rtengine::RT_PI;
|
|
//fprintf(stderr, "%f %f %f %f %f %d %d\n", gradient_stops, gradient_span, gradient_center_x, gradient_center_y, gradient_angle, w, h);
|
|
|
|
// make 0.0 <= gradient_angle < 2 * rtengine::RT_PI
|
|
gradient_angle = fmod (gradient_angle, 2 * rtengine::RT_PI);
|
|
|
|
if (gradient_angle < 0.0) {
|
|
gradient_angle += 2.0 * rtengine::RT_PI;
|
|
}
|
|
|
|
gp.bright_top = false;
|
|
gp.transpose = false;
|
|
gp.angle_is_zero = false;
|
|
gp.h = h;
|
|
double cosgrad = cos (gradient_angle);
|
|
|
|
if (fabs (cosgrad) < 0.707) {
|
|
// we transpose to avoid division by zero at 90 degrees
|
|
// (actually we could transpose only for 90 degrees, but this way we avoid
|
|
// division with extremely small numbers
|
|
gp.transpose = true;
|
|
gradient_angle += 0.5 * rtengine::RT_PI;
|
|
double gxc = gradient_center_x;
|
|
gradient_center_x = 1.0 - gradient_center_y;
|
|
gradient_center_y = gxc;
|
|
}
|
|
|
|
gradient_angle = fmod (gradient_angle, 2 * rtengine::RT_PI);
|
|
|
|
if (gradient_angle > 0.5 * rtengine::RT_PI && gradient_angle < rtengine::RT_PI) {
|
|
gradient_angle += rtengine::RT_PI;
|
|
gp.bright_top = true;
|
|
} else if (gradient_angle >= rtengine::RT_PI && gradient_angle < 1.5 * rtengine::RT_PI) {
|
|
gradient_angle -= rtengine::RT_PI;
|
|
gp.bright_top = true;
|
|
}
|
|
|
|
if (fabs (gradient_angle) < 0.001 || fabs (gradient_angle - 2 * rtengine::RT_PI) < 0.001) {
|
|
gradient_angle = 0;
|
|
gp.angle_is_zero = true;
|
|
}
|
|
|
|
if (gp.transpose) {
|
|
gp.bright_top = !gp.bright_top;
|
|
std::swap(w, h);
|
|
}
|
|
|
|
gp.scale = 1.0 / pow (2, gradient_stops);
|
|
|
|
if (gp.bright_top) {
|
|
gp.topmul = 1.0;
|
|
gp.botmul = gp.scale;
|
|
} else {
|
|
gp.topmul = gp.scale;
|
|
gp.botmul = 1.0;
|
|
}
|
|
|
|
gp.ta = tan (gradient_angle);
|
|
gp.xc = w * gradient_center_x;
|
|
gp.yc = h * gradient_center_y;
|
|
gp.ys = sqrt ((float)h * h + (float)w * w) * (gradient_span / cos (gradient_angle));
|
|
gp.ys_inv = 1.0 / gp.ys;
|
|
gp.top_edge_0 = gp.yc - gp.ys / 2.0;
|
|
|
|
if (gp.ys < 1.0 / h) {
|
|
gp.ys_inv = 0;
|
|
gp.ys = 0;
|
|
}
|
|
}
|
|
|
|
static float calcGradientFactor (const struct grad_params& gp, int x, int y)
|
|
{
|
|
if (gp.angle_is_zero) {
|
|
int gy = gp.transpose ? x : y;
|
|
|
|
if (gy < gp.top_edge_0) {
|
|
return gp.topmul;
|
|
} else if (gy >= gp.top_edge_0 + gp.ys) {
|
|
return gp.botmul;
|
|
} else {
|
|
float val = ((float) (gy - gp.top_edge_0) * gp.ys_inv);
|
|
|
|
if (gp.bright_top) {
|
|
val = 1.f - val;
|
|
}
|
|
|
|
val *= rtengine::RT_PI_F_2;
|
|
|
|
if (gp.scale < 1.f) {
|
|
val = pow3 (xsinf (val));
|
|
} else {
|
|
val = 1.f - pow3 (xcosf (val));
|
|
}
|
|
|
|
return gp.scale + val * (1.0 - gp.scale);
|
|
}
|
|
} else {
|
|
int gy = gp.transpose ? x : y;
|
|
int gx = gp.transpose ? gp.h - y - 1 : x;
|
|
float top_edge = gp.top_edge_0 - gp.ta * (gx - gp.xc);
|
|
|
|
if (gy < top_edge) {
|
|
return gp.topmul;
|
|
} else if (gy >= top_edge + gp.ys) {
|
|
return gp.botmul;
|
|
} else {
|
|
float val = ((float) (gy - top_edge) * gp.ys_inv);
|
|
|
|
val = gp.bright_top ? 1.f - val : val;
|
|
|
|
val *= rtengine::RT_PI_F_2;
|
|
|
|
if (gp.scale < 1.f) {
|
|
val = pow3 (xsinf (val));
|
|
} else {
|
|
val = 1.f - pow3 (xcosf (val));
|
|
}
|
|
|
|
return gp.scale + val * (1.0 - gp.scale);
|
|
}
|
|
}
|
|
}
|
|
|
|
struct pcv_params {
|
|
float oe_a, oe_b, oe1_a, oe1_b, oe2_a, oe2_b;
|
|
float ie_mul, ie1_mul, ie2_mul;
|
|
float sepmix, feather;
|
|
int w, h, x1, x2, y1, y2;
|
|
int sep;
|
|
bool is_super_ellipse_mode, is_portrait;
|
|
float scale;
|
|
float fadeout_mul;
|
|
};
|
|
static void calcPCVignetteParams (int fW, int fH, int oW, int oH, const PCVignetteParams& pcvignette, const CropParams &crop, struct pcv_params& pcv)
|
|
{
|
|
|
|
// ellipse formula: (x/a)^2 + (y/b)^2 = 1
|
|
double roundness = pcvignette.roundness / 100.0;
|
|
pcv.feather = pcvignette.feather / 100.0;
|
|
|
|
if (crop.enabled) {
|
|
pcv.w = (crop.w * oW) / fW;
|
|
pcv.h = (crop.h * oH) / fH;
|
|
pcv.x1 = (crop.x * oW) / fW;
|
|
pcv.y1 = (crop.y * oH) / fH;
|
|
pcv.x2 = pcv.x1 + pcv.w;
|
|
pcv.y2 = pcv.y1 + pcv.h;
|
|
} else {
|
|
pcv.x1 = 0, pcv.y1 = 0;
|
|
pcv.x2 = oW, pcv.y2 = oH;
|
|
pcv.w = oW;
|
|
pcv.h = oH;
|
|
}
|
|
|
|
pcv.fadeout_mul = 1.0 / (0.05 * sqrtf (oW * oW + oH * oH));
|
|
float short_side = (pcv.w < pcv.h) ? pcv.w : pcv.h;
|
|
float long_side = (pcv.w > pcv.h) ? pcv.w : pcv.h;
|
|
|
|
pcv.sep = 2;
|
|
pcv.sepmix = 0;
|
|
pcv.oe_a = sqrt (2.0) * long_side * 0.5;
|
|
pcv.oe_b = pcv.oe_a * short_side / long_side;
|
|
pcv.ie_mul = (1.0 / sqrt (2.0)) * (1.0 - pcv.feather);
|
|
pcv.is_super_ellipse_mode = false;
|
|
pcv.is_portrait = (pcv.w < pcv.h);
|
|
|
|
if (roundness < 0.5) {
|
|
// make super-ellipse of higher and higher degree
|
|
pcv.is_super_ellipse_mode = true;
|
|
float sepf = 2 + 4 * powf (1.0 - 2 * roundness, 1.3); // gamma 1.3 used to balance the effect in the 0.0...0.5 roundness range
|
|
pcv.sep = ((int)sepf) & ~0x1;
|
|
pcv.sepmix = (sepf - pcv.sep) * 0.5; // 0.0 to 1.0
|
|
pcv.oe1_a = powf (2.0, 1.0 / pcv.sep) * long_side * 0.5;
|
|
pcv.oe1_b = pcv.oe1_a * short_side / long_side;
|
|
pcv.ie1_mul = (1.0 / powf (2.0, 1.0 / pcv.sep)) * (1.0 - pcv.feather);
|
|
pcv.oe2_a = powf (2.0, 1.0 / (pcv.sep + 2)) * long_side * 0.5;
|
|
pcv.oe2_b = pcv.oe2_a * short_side / long_side;
|
|
pcv.ie2_mul = (1.0 / powf (2.0, 1.0 / (pcv.sep + 2))) * (1.0 - pcv.feather);
|
|
}
|
|
|
|
if (roundness > 0.5) {
|
|
// scale from fitted ellipse towards circle
|
|
float rad = sqrtf (pcv.w * pcv.w + pcv.h * pcv.h) / 2.0;
|
|
float diff_a = rad - pcv.oe_a;
|
|
float diff_b = rad - pcv.oe_b;
|
|
pcv.oe_a = pcv.oe_a + diff_a * 2 * (roundness - 0.5);
|
|
pcv.oe_b = pcv.oe_b + diff_b * 2 * (roundness - 0.5);
|
|
}
|
|
|
|
pcv.scale = powf (2, -pcvignette.strength);
|
|
|
|
if (pcvignette.strength >= 6.0) {
|
|
pcv.scale = 0.0;
|
|
}
|
|
}
|
|
|
|
static float calcPCVignetteFactor (const struct pcv_params& pcv, int x, int y)
|
|
{
|
|
|
|
float fo = 1.f;
|
|
|
|
if (x < pcv.x1 || x > pcv.x2 || y < pcv.y1 || y > pcv.y2) {
|
|
/*
|
|
The initial plan was to have 1.0 directly outside the crop box (ie no fading), but due to
|
|
rounding/trunction here and there I didn't succeed matching up exactly on the pixel with
|
|
the crop box. To hide that mismatch I made a fade.
|
|
*/
|
|
int dist_x = (x < pcv.x1) ? pcv.x1 - x : x - pcv.x2;
|
|
int dist_y = (y < pcv.y1) ? pcv.y1 - y : y - pcv.y2;
|
|
|
|
if (dist_x < 0) {
|
|
dist_x = 0;
|
|
}
|
|
|
|
if (dist_y < 0) {
|
|
dist_y = 0;
|
|
}
|
|
|
|
fo = sqrtf (dist_x * dist_x + dist_y * dist_y) * pcv.fadeout_mul;
|
|
|
|
if (fo >= 1.f) {
|
|
return 1.f;
|
|
}
|
|
}
|
|
|
|
float a = fabs ((x - pcv.x1) - pcv.w * 0.5f);
|
|
float b = fabs ((y - pcv.y1) - pcv.h * 0.5f);
|
|
|
|
if (pcv.is_portrait) {
|
|
std::swap (a, b);
|
|
}
|
|
|
|
float dist = normn (a, b, 2);
|
|
float dist_oe, dist_ie;
|
|
float2 sincosval;
|
|
|
|
if (dist == 0.0f) {
|
|
sincosval.y = 1.0f; // cos
|
|
sincosval.x = 0.0f; // sin
|
|
} else {
|
|
sincosval.y = a / dist; // cos
|
|
sincosval.x = b / dist; // sin
|
|
}
|
|
|
|
if (pcv.is_super_ellipse_mode) {
|
|
float dist_oe1 = pcv.oe1_a * pcv.oe1_b / normn (pcv.oe1_b * sincosval.y, pcv.oe1_a * sincosval.x, pcv.sep);
|
|
float dist_oe2 = pcv.oe2_a * pcv.oe2_b / normn (pcv.oe2_b * sincosval.y, pcv.oe2_a * sincosval.x, pcv.sep + 2);
|
|
float dist_ie1 = pcv.ie1_mul * dist_oe1;
|
|
float dist_ie2 = pcv.ie2_mul * dist_oe2;
|
|
dist_oe = dist_oe1 * (1.f - pcv.sepmix) + dist_oe2 * pcv.sepmix;
|
|
dist_ie = dist_ie1 * (1.f - pcv.sepmix) + dist_ie2 * pcv.sepmix;
|
|
} else {
|
|
dist_oe = pcv.oe_a * pcv.oe_b / sqrtf (SQR (pcv.oe_b * sincosval.y) + SQR (pcv.oe_a * sincosval.x));
|
|
dist_ie = pcv.ie_mul * dist_oe;
|
|
}
|
|
|
|
if (dist <= dist_ie) {
|
|
return 1.f;
|
|
}
|
|
|
|
float val;
|
|
|
|
if (dist >= dist_oe) {
|
|
val = pcv.scale;
|
|
} else {
|
|
val = rtengine::RT_PI_F_2 * (dist - dist_ie) / (dist_oe - dist_ie);
|
|
|
|
if (pcv.scale < 1.f) {
|
|
val = pow4 (xcosf (val));
|
|
} else {
|
|
val = 1 - pow4 (xsinf (val));
|
|
}
|
|
|
|
val = pcv.scale + val * (1.f - pcv.scale);
|
|
}
|
|
|
|
if (fo < 1.f) {
|
|
val = fo + val * (1.f - fo);
|
|
}
|
|
|
|
return val;
|
|
}
|
|
|
|
void ImProcFunctions::transformLuminanceOnly (Imagefloat* original, Imagefloat* transformed, int cx, int cy, int oW, int oH, int fW, int fH)
|
|
{
|
|
|
|
const bool applyVignetting = needsVignetting();
|
|
const bool applyGradient = needsGradient();
|
|
const bool applyPCVignetting = needsPCVignetting();
|
|
|
|
double vig_w2, vig_h2, maxRadius, v, b, mul;
|
|
|
|
if (applyVignetting) {
|
|
calcVignettingParams (oW, oH, params->vignetting, vig_w2, vig_h2, maxRadius, v, b, mul);
|
|
}
|
|
|
|
struct grad_params gp;
|
|
|
|
if (applyGradient) {
|
|
calcGradientParams (oW, oH, params->gradient, gp);
|
|
}
|
|
|
|
struct pcv_params pcv;
|
|
|
|
if (applyPCVignetting) {
|
|
//fprintf(stderr, "%d %d | %d %d | %d %d | %d %d [%d %d]\n", fW, fH, oW, oH, transformed->getWidth(), transformed->getHeight(), cx, cy, params->crop.w, params->crop.h);
|
|
calcPCVignetteParams (fW, fH, oW, oH, params->pcvignette, params->crop, pcv);
|
|
}
|
|
|
|
bool darkening = (params->vignetting.amount <= 0.0);
|
|
#ifdef _OPENMP
|
|
#pragma omp parallel for schedule(dynamic,16) if (multiThread)
|
|
#endif
|
|
|
|
for (int y = 0; y < transformed->getHeight(); y++) {
|
|
double vig_y_d = applyVignetting ? (double) (y + cy) - vig_h2 : 0.0;
|
|
|
|
for (int x = 0; x < transformed->getWidth(); x++) {
|
|
double factor = 1.0;
|
|
|
|
if (applyVignetting) {
|
|
double vig_x_d = (double) (x + cx) - vig_w2 ;
|
|
double r = sqrt (vig_x_d * vig_x_d + vig_y_d * vig_y_d);
|
|
|
|
if (darkening) {
|
|
factor /= std::max (v + mul * tanh (b * (maxRadius - r) / maxRadius), 0.001);
|
|
} else {
|
|
factor = v + mul * tanh (b * (maxRadius - r) / maxRadius);
|
|
}
|
|
}
|
|
|
|
if (applyGradient) {
|
|
factor *= calcGradientFactor (gp, cx + x, cy + y);
|
|
}
|
|
|
|
if (applyPCVignetting) {
|
|
factor *= calcPCVignetteFactor (pcv, cx + x, cy + y);
|
|
}
|
|
|
|
transformed->r (y, x) = original->r (y, x) * factor;
|
|
transformed->g (y, x) = original->g (y, x) * factor;
|
|
transformed->b (y, x) = original->b (y, x) * factor;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void ImProcFunctions::transformGeneral(bool highQuality, Imagefloat *original, Imagefloat *transformed, int cx, int cy, int sx, int sy, int oW, int oH, int fW, int fH, const LensCorrection *pLCPMap)
|
|
{
|
|
// set up stuff, depending on the mode we are
|
|
const bool enableLCPDist = pLCPMap && params->lensProf.useDist;
|
|
const bool enableCA = highQuality && needsCA();
|
|
const bool enableGradient = needsGradient();
|
|
const bool enablePCVignetting = needsPCVignetting();
|
|
const bool enableVignetting = needsVignetting();
|
|
const bool enablePerspective = needsPerspective();
|
|
const bool enableDistortion = needsDistortion();
|
|
|
|
const double w2 = static_cast<double>(oW) / 2.0 - 0.5;
|
|
const double h2 = static_cast<double>(oH) / 2.0 - 0.5;
|
|
|
|
double vig_w2, vig_h2, maxRadius, v, b, mul;
|
|
calcVignettingParams(oW, oH, params->vignetting, vig_w2, vig_h2, maxRadius, v, b, mul);
|
|
|
|
grad_params gp;
|
|
|
|
if (enableGradient) {
|
|
calcGradientParams(oW, oH, params->gradient, gp);
|
|
}
|
|
|
|
pcv_params pcv;
|
|
|
|
if (enablePCVignetting) {
|
|
calcPCVignetteParams(fW, fH, oW, oH, params->pcvignette, params->crop, pcv);
|
|
}
|
|
|
|
const std::array<const float* const*, 3> chOrig = {
|
|
original->r.ptrs,
|
|
original->g.ptrs,
|
|
original->b.ptrs
|
|
};
|
|
const std::array<float* const*, 3> chTrans = {
|
|
transformed->r.ptrs,
|
|
transformed->g.ptrs,
|
|
transformed->b.ptrs
|
|
};
|
|
|
|
// auxiliary variables for c/a correction
|
|
const std::array<double, 3> chDist = {
|
|
enableCA
|
|
? params->cacorrection.red
|
|
: 0.0,
|
|
0.0,
|
|
enableCA
|
|
? params->cacorrection.blue
|
|
: 0.0
|
|
};
|
|
|
|
// auxiliary variables for distortion correction
|
|
const double distAmount = params->distortion.amount;
|
|
|
|
// auxiliary variables for rotation
|
|
const double cost = cos(params->rotate.degree * rtengine::RT_PI / 180.0);
|
|
const double sint = sin(params->rotate.degree * rtengine::RT_PI / 180.0);
|
|
|
|
const double ascale = params->commonTrans.autofill ? getTransformAutoFill(oW, oH, pLCPMap) : 1.0;
|
|
|
|
const bool darkening = (params->vignetting.amount <= 0.0);
|
|
const double centerFactorx = cx - w2;
|
|
const double centerFactory = cy - h2;
|
|
|
|
// auxiliary variables for perspective correction
|
|
const double f = maxRadius / tan(params->perspective.fov / 360.0 * rtengine::RT_PI);
|
|
const double phtheta = params->perspective.horizontal / -180.0 * rtengine::RT_PI;
|
|
const double pvtheta = params->perspective.vertical / -180.0 * rtengine::RT_PI;
|
|
const double pbtheta = params->perspective.vBias / -180.0 * rtengine::RT_PI;
|
|
const double phcos = cos(phtheta);
|
|
const double pvcos = cos(pvtheta);
|
|
const double pbcos = cos(pbtheta);
|
|
const double phsin = sin(phtheta);
|
|
const double pvsin = sin(pvtheta);
|
|
const double pbsin = sin(pbtheta);
|
|
// Coordinates of distorted image center.
|
|
const double pxoffset = f * phsin * pvcos;
|
|
const double pyoffset = -f * (phcos * pvcos * pbsin + pvsin * pbcos);
|
|
const double pz = f * (phcos * pvcos * pbcos - pvsin * pbsin);
|
|
// Inverse transformation matrix.
|
|
const double p_xx = f * phcos;
|
|
const double p_xy = f * phsin * pbsin;
|
|
const double p_xz = f * phsin * pbcos;
|
|
const double p_yx = f * phsin * pvsin;
|
|
const double p_yy = f * (pvcos * pbcos - phcos * pvsin * pbsin);
|
|
const double p_yz = f * (-pvcos * pbsin - phcos * pvsin * pbcos);
|
|
const double p_zx = -phsin * pvcos;
|
|
const double p_zy = phcos * pvcos * pbsin + pvsin * pbcos;
|
|
const double p_zz = phcos * pvcos * pbcos - pvsin * pbsin;
|
|
// z is known, can calculate these in advance.
|
|
const double pz_xz = pz * p_xz;
|
|
const double pz_yz = pz * p_yz;
|
|
const double pz_zz = pz * p_zz;
|
|
|
|
// main cycle
|
|
#ifdef _OPENMP
|
|
#pragma omp parallel for schedule(dynamic, 16) if(multiThread)
|
|
#endif
|
|
|
|
for (int y = 0; y < transformed->getHeight(); ++y) {
|
|
for (int x = 0; x < transformed->getWidth(); ++x) {
|
|
double x_d = x;
|
|
double y_d = y;
|
|
|
|
if (enableLCPDist) {
|
|
pLCPMap->correctDistortion(x_d, y_d, cx, cy, ascale); // must be first transform
|
|
} else {
|
|
x_d *= ascale;
|
|
y_d *= ascale;
|
|
}
|
|
|
|
x_d += ascale * centerFactorx; // centering x coord & scale
|
|
y_d += ascale * centerFactory; // centering y coord & scale
|
|
|
|
if (enablePerspective) {
|
|
x_d -= pxoffset;
|
|
y_d -= pyoffset;
|
|
const double normalizer = p_zx * x_d + p_zy * y_d + pz_zz;
|
|
const double x_d_new = p_xx * x_d + p_xy * y_d + pz_xz;
|
|
y_d = p_yx * x_d + p_yy * y_d + pz_yz;
|
|
x_d = x_d_new / normalizer;
|
|
y_d /= normalizer;
|
|
}
|
|
|
|
// rotate
|
|
const double Dxc = x_d * cost - y_d * sint;
|
|
const double Dyc = x_d * sint + y_d * cost;
|
|
|
|
// distortion correction
|
|
double s = 1.0;
|
|
|
|
if (enableDistortion) {
|
|
const double r = sqrt(Dxc * Dxc + Dyc * Dyc) / maxRadius;
|
|
s = 1.0 - distAmount + distAmount * r;
|
|
}
|
|
|
|
for (int c = 0; c < (enableCA ? 3 : 1); ++c) {
|
|
double Dx = Dxc * (s + chDist[c]);
|
|
double Dy = Dyc * (s + chDist[c]);
|
|
|
|
// de-center
|
|
Dx += w2;
|
|
Dy += h2;
|
|
|
|
// Extract integer and fractions of source screen coordinates
|
|
int xc = Dx;
|
|
Dx -= xc;
|
|
xc -= sx;
|
|
int yc = Dy;
|
|
Dy -= yc;
|
|
yc -= sy;
|
|
|
|
// Convert only valid pixels
|
|
if (yc >= 0 && yc < original->getHeight() && xc >= 0 && xc < original->getWidth()) {
|
|
// multiplier for vignetting correction
|
|
double vignmul = 1.0;
|
|
|
|
if (enableVignetting) {
|
|
const double vig_x_d = ascale * (x + cx - vig_w2); // centering x coord & scale
|
|
const double vig_y_d = ascale * (y + cy - vig_h2); // centering y coord & scale
|
|
const double vig_Dx = vig_x_d * cost - vig_y_d * sint;
|
|
const double vig_Dy = vig_x_d * sint + vig_y_d * cost;
|
|
const double r2 = sqrt(vig_Dx * vig_Dx + vig_Dy * vig_Dy);
|
|
if (darkening) {
|
|
vignmul /= std::max(v + mul * tanh(b * (maxRadius - s * r2) / maxRadius), 0.001);
|
|
} else {
|
|
vignmul *= (v + mul * tanh(b * (maxRadius - s * r2) / maxRadius));
|
|
}
|
|
}
|
|
|
|
if (enableGradient) {
|
|
vignmul *= calcGradientFactor(gp, cx + x, cy + y);
|
|
}
|
|
|
|
if (enablePCVignetting) {
|
|
vignmul *= calcPCVignetteFactor(pcv, cx + x, cy + y);
|
|
}
|
|
|
|
if (yc > 0 && yc < original->getHeight() - 2 && xc > 0 && xc < original->getWidth() - 2) {
|
|
// all interpolation pixels inside image
|
|
if (enableCA) {
|
|
interpolateTransformChannelsCubic(chOrig[c], xc - 1, yc - 1, Dx, Dy, chTrans[c][y][x], vignmul);
|
|
} else if (!highQuality) {
|
|
transformed->r(y, x) = vignmul * (original->r(yc, xc) * (1.0 - Dx) * (1.0 - Dy) + original->r(yc, xc + 1) * Dx * (1.0 - Dy) + original->r(yc + 1, xc) * (1.0 - Dx) * Dy + original->r(yc + 1, xc + 1) * Dx * Dy);
|
|
transformed->g(y, x) = vignmul * (original->g(yc, xc) * (1.0 - Dx) * (1.0 - Dy) + original->g(yc, xc + 1) * Dx * (1.0 - Dy) + original->g(yc + 1, xc) * (1.0 - Dx) * Dy + original->g(yc + 1, xc + 1) * Dx * Dy);
|
|
transformed->b(y, x) = vignmul * (original->b(yc, xc) * (1.0 - Dx) * (1.0 - Dy) + original->b(yc, xc + 1) * Dx * (1.0 - Dy) + original->b(yc + 1, xc) * (1.0 - Dx) * Dy + original->b(yc + 1, xc + 1) * Dx * Dy);
|
|
} else {
|
|
interpolateTransformCubic(original, xc - 1, yc - 1, Dx, Dy, transformed->r(y, x), transformed->g(y, x), transformed->b(y, x), vignmul);
|
|
}
|
|
} else {
|
|
// edge pixels
|
|
const int y1 = LIM(yc, 0, original->getHeight() - 1);
|
|
const int y2 = LIM(yc + 1, 0, original->getHeight() - 1);
|
|
const int x1 = LIM(xc, 0, original->getWidth() - 1);
|
|
const int x2 = LIM(xc + 1, 0, original->getWidth() - 1);
|
|
|
|
if (enableCA) {
|
|
chTrans[c][y][x] = vignmul * (chOrig[c][y1][x1] * (1.0 - Dx) * (1.0 - Dy) + chOrig[c][y1][x2] * Dx * (1.0 - Dy) + chOrig[c][y2][x1] * (1.0 - Dx) * Dy + chOrig[c][y2][x2] * Dx * Dy);
|
|
} else {
|
|
transformed->r(y, x) = vignmul * (original->r(y1, x1) * (1.0 - Dx) * (1.0 - Dy) + original->r(y1, x2) * Dx * (1.0 - Dy) + original->r(y2, x1) * (1.0 - Dx) * Dy + original->r(y2, x2) * Dx * Dy);
|
|
transformed->g(y, x) = vignmul * (original->g(y1, x1) * (1.0 - Dx) * (1.0 - Dy) + original->g(y1, x2) * Dx * (1.0 - Dy) + original->g(y2, x1) * (1.0 - Dx) * Dy + original->g(y2, x2) * Dx * Dy);
|
|
transformed->b(y, x) = vignmul * (original->b(y1, x1) * (1.0 - Dx) * (1.0 - Dy) + original->b(y1, x2) * Dx * (1.0 - Dy) + original->b(y2, x1) * (1.0 - Dx) * Dy + original->b(y2, x2) * Dx * Dy);
|
|
}
|
|
}
|
|
} else {
|
|
if (enableCA) {
|
|
// not valid (source pixel x,y not inside source image, etc.)
|
|
chTrans[c][y][x] = 0;
|
|
} else {
|
|
transformed->r(y, x) = 0;
|
|
transformed->g(y, x) = 0;
|
|
transformed->b(y, x) = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void ImProcFunctions::transformLCPCAOnly(Imagefloat *original, Imagefloat *transformed, int cx, int cy, const LensCorrection *pLCPMap)
|
|
{
|
|
assert(pLCPMap && params->lensProf.useCA && pLCPMap->isCACorrectionAvailable());
|
|
|
|
float** chOrig[3];
|
|
chOrig[0] = original->r.ptrs;
|
|
chOrig[1] = original->g.ptrs;
|
|
chOrig[2] = original->b.ptrs;
|
|
|
|
float** chTrans[3];
|
|
chTrans[0] = transformed->r.ptrs;
|
|
chTrans[1] = transformed->g.ptrs;
|
|
chTrans[2] = transformed->b.ptrs;
|
|
|
|
#ifdef _OPENMP
|
|
#pragma omp parallel for if (multiThread)
|
|
#endif
|
|
|
|
for (int y = 0; y < transformed->getHeight(); y++) {
|
|
for (int x = 0; x < transformed->getWidth(); x++) {
|
|
for (int c = 0; c < 3; c++) {
|
|
double Dx = x;
|
|
double Dy = y;
|
|
|
|
pLCPMap->correctCA(Dx, Dy, cx, cy, c);
|
|
|
|
// Extract integer and fractions of coordinates
|
|
int xc = (int)Dx;
|
|
Dx -= (double)xc;
|
|
int yc = (int)Dy;
|
|
Dy -= (double)yc;
|
|
|
|
// Convert only valid pixels
|
|
if (yc >= 0 && yc < original->getHeight() && xc >= 0 && xc < original->getWidth()) {
|
|
|
|
// multiplier for vignetting correction
|
|
if (yc > 0 && yc < original->getHeight() - 2 && xc > 0 && xc < original->getWidth() - 2) {
|
|
// all interpolation pixels inside image
|
|
interpolateTransformChannelsCubic (chOrig[c], xc - 1, yc - 1, Dx, Dy, chTrans[c][y][x], 1.0);
|
|
} else {
|
|
// edge pixels
|
|
int y1 = LIM (yc, 0, original->getHeight() - 1);
|
|
int y2 = LIM (yc + 1, 0, original->getHeight() - 1);
|
|
int x1 = LIM (xc, 0, original->getWidth() - 1);
|
|
int x2 = LIM (xc + 1, 0, original->getWidth() - 1);
|
|
|
|
chTrans[c][y][x] = (chOrig[c][y1][x1] * (1.0 - Dx) * (1.0 - Dy) + chOrig[c][y1][x2] * Dx * (1.0 - Dy) + chOrig[c][y2][x1] * (1.0 - Dx) * Dy + chOrig[c][y2][x2] * Dx * Dy);
|
|
}
|
|
} else {
|
|
// not valid (source pixel x,y not inside source image, etc.)
|
|
chTrans[c][y][x] = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
double ImProcFunctions::getTransformAutoFill (int oW, int oH, const LensCorrection *pLCPMap) const
|
|
{
|
|
if (!needsCA() && !needsDistortion() && !needsRotation() && !needsPerspective() && (!params->lensProf.useDist || pLCPMap == nullptr)) {
|
|
return 1;
|
|
}
|
|
|
|
double scaleU = 2, scaleL = 0.001; // upper and lower border, iterate inbetween
|
|
|
|
do {
|
|
double scale = (scaleU + scaleL) * 0.5;
|
|
|
|
int orx, ory, orw, orh;
|
|
bool clipped = transCoord (oW, oH, 0, 0, oW, oH, orx, ory, orw, orh, scale, pLCPMap);
|
|
|
|
if (clipped) {
|
|
scaleU = scale;
|
|
} else {
|
|
scaleL = scale;
|
|
}
|
|
} while (scaleU - scaleL > 0.001);
|
|
|
|
return scaleL;
|
|
}
|
|
|
|
bool ImProcFunctions::needsCA () const
|
|
{
|
|
return fabs (params->cacorrection.red) > 1e-15 || fabs (params->cacorrection.blue) > 1e-15;
|
|
}
|
|
|
|
bool ImProcFunctions::needsDistortion () const
|
|
{
|
|
return fabs (params->distortion.amount) > 1e-15;
|
|
}
|
|
|
|
bool ImProcFunctions::needsRotation () const
|
|
{
|
|
return fabs (params->rotate.degree) > 1e-15;
|
|
}
|
|
|
|
bool ImProcFunctions::needsPerspective () const
|
|
{
|
|
return params->perspective.horizontal || params->perspective.vertical || params->perspective.vBias;
|
|
}
|
|
|
|
bool ImProcFunctions::needsGradient () const
|
|
{
|
|
return params->gradient.enabled && fabs (params->gradient.strength) > 1e-15;
|
|
}
|
|
|
|
bool ImProcFunctions::needsPCVignetting () const
|
|
{
|
|
return params->pcvignette.enabled && fabs (params->pcvignette.strength) > 1e-15;
|
|
}
|
|
|
|
bool ImProcFunctions::needsVignetting () const
|
|
{
|
|
return params->vignetting.amount;
|
|
}
|
|
|
|
bool ImProcFunctions::needsLCP () const
|
|
{
|
|
return params->lensProf.useLcp();
|
|
}
|
|
|
|
bool ImProcFunctions::needsLensfun() const
|
|
{
|
|
return params->lensProf.useLensfun();
|
|
}
|
|
|
|
bool ImProcFunctions::needsTransform () const
|
|
{
|
|
return needsCA () || needsDistortion () || needsRotation () || needsPerspective () || needsGradient () || needsPCVignetting () || needsVignetting () || needsLCP() || needsLensfun();
|
|
}
|
|
|
|
|
|
}
|
|
|