rawTherapee/rtengine/perspectivecorrection.cc
Daniel Gao 711f274403 Get rid of relative include paths
* Use target_include_directories to specify include paths
* Specify project root (parent of rtgui and rtengine) as include path
* Replace relative includes with normal includes
2024-11-16 17:20:02 -05:00

417 lines
13 KiB
C++

/* -*- C++ -*-
*
* This file is part of RawTherapee.
*
* Copyright (c) 2019 Alberto Griggio <alberto.griggio@gmail.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/>.
*/
// taken from darktable (src/iop/ashift.c)
/*
This file is part of darktable,
copyright (c) 2016 Ulrich Pegelow.
darktable 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.
darktable 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 darktable. If not, see <http://www.gnu.org/licenses/>.
*/
// Inspiration to this module comes from the program ShiftN (http://www.shiftn.de) by
// Marcus Hebel.
// Thanks to Marcus for his support when implementing part of the ShiftN functionality
// to darktable.
#include "perspectivecorrection.h"
#include "improcfun.h"
#include "procparams.h"
#include "rt_math.h"
#include <string.h>
#include <math.h>
#include <assert.h>
#include <inttypes.h>
#include <math.h>
#include <stdlib.h>
#include <string.h>
#include "rtgui/threadutils.h"
#include "colortemp.h"
#include "imagefloat.h"
#include "settings.h"
namespace rtengine { extern const Settings *settings; }
#define _(msg) (msg)
#define dt_control_log(msg) \
if (settings->verbose) { \
printf("%s\n", msg); \
fflush(stdout); \
}
namespace rtengine {
namespace {
inline int mat3inv(float *const dst, const float *const src)
{
std::array<std::array<float, 3>, 3> tmpsrc;
std::array<std::array<float, 3>, 3> tmpdst;
for (int i = 0; i < 3; ++i) {
for (int j = 0; j < 3; ++j) {
tmpsrc[i][j] = src[3 * i + j];
}
}
if (invertMatrix(tmpsrc, tmpdst)) {
for (int i = 0; i < 3; ++i) {
for (int j = 0; j < 3; ++j) {
dst[3 * i + j] = tmpdst[i][j];
}
}
return 0;
} else {
return 1;
}
}
// the darktable ashift iop (adapted to RT), which does most of the work
#include "ashift_dt.c"
} // namespace
namespace {
/*
std::vector<Coord2D> get_corners(int w, int h)
{
int x1 = 0, y1 = 0;
int x2 = w, y2 = h;
std::vector<Coord2D> corners = {
Coord2D(x1, y1),
Coord2D(x1, y2),
Coord2D(x2, y2),
Coord2D(x2, y1)
};
return corners;
}
*/
void init_dt_structures(dt_iop_ashift_params_t *p, dt_iop_ashift_gui_data_t *g,
const procparams::PerspectiveParams *params)
{
dt_iop_ashift_params_t dp = {
0.0f,
0.0f,
0.0f,
0.0f,
DEFAULT_F_LENGTH,
1.f,
0.0f,
1.0f,
ASHIFT_MODE_SPECIFIC,
0,
ASHIFT_CROP_OFF,
0.0f,
1.0f,
0.0f,
1.0f,
0.0f,
0.0f
};
*p = dp;
g->buf = NULL;
g->buf_width = 0;
g->buf_height = 0;
g->buf_x_off = 0;
g->buf_y_off = 0;
g->buf_scale = 1.0f;
g->buf_hash = 0;
g->isflipped = 0;
g->lastfit = ASHIFT_FIT_NONE;
g->fitting = 0;
g->lines = NULL;
g->lines_count =0;
g->horizontal_count = 0;
g->vertical_count = 0;
g->grid_hash = 0;
g->lines_hash = 0;
g->rotation_range = ROTATION_RANGE_SOFT;
g->lensshift_v_range = LENSSHIFT_RANGE_SOFT;
g->lensshift_h_range = LENSSHIFT_RANGE_SOFT;
g->shear_range = SHEAR_RANGE_SOFT;
g->camera_pitch_range = CAMERA_ANGLE_RANGE_SOFT;
g->camera_yaw_range = CAMERA_ANGLE_RANGE_SOFT;
g->lines_suppressed = 0;
g->lines_version = 0;
g->show_guides = 0;
g->isselecting = 0;
g->isdeselecting = 0;
g->isbounding = ASHIFT_BOUNDING_OFF;
g->near_delta = 0;
g->selecting_lines_version = 0;
g->points = NULL;
g->points_idx = NULL;
g->points_lines_count = 0;
g->points_version = 0;
g->jobcode = ASHIFT_JOBCODE_NONE;
g->jobparams = 0;
g->adjust_crop = FALSE;
g->lastx = g->lasty = -1.0f;
g->crop_cx = g->crop_cy = 1.0f;
if (params) {
p->rotation = params->camera_roll;
p->lensshift_v = params->camera_shift_vert;
p->lensshift_h = params->camera_shift_horiz;
p->f_length = params->camera_focal_length;
p->crop_factor = params->camera_crop_factor;
p->camera_pitch = params->camera_pitch;
p->camera_yaw = params->camera_yaw;
}
}
/*
void get_view_size(int w, int h, const procparams::PerspectiveParams &params, double &cw, double &ch)
{
double min_x = RT_INFINITY, max_x = -RT_INFINITY;
double min_y = RT_INFINITY, max_y = -RT_INFINITY;
auto corners = get_corners(w, h);
float homo[3][3];
homography((float *)homo, params.angle, params.vertical / 100.0, -params.horizontal / 100.0, params.shear / 100.0, params.flength * params.cropfactor, 100.f, params.aspect, w, h, ASHIFT_HOMOGRAPH_FORWARD);
for (auto &c : corners) {
float pin[3] = { float(c.x), float(c.y), 1.f };
float pout[3];
mat3mulv(pout, (float *)homo, pin);
double x = pout[0] / pout[2];
double y = pout[1] / pout[2];
min_x = min(min_x, x);
max_x = max(max_x, x);
min_y = min(min_y, y);
max_y = max(max_y, y);
}
cw = max_x - min_x;
ch = max_y - min_y;
}
*/
/**
* Allocates a new array and populates it with ashift lines corresponding to the
* provided control lines.
*/
std::unique_ptr<dt_iop_ashift_line_t[]> toAshiftLines(const std::vector<ControlLine> *lines)
{
std::unique_ptr<dt_iop_ashift_line_t[]> retval(new dt_iop_ashift_line_t[lines->size()]);
for (size_t i = 0; i < lines->size(); i++) {
const float x1 = (*lines)[i].x1;
const float y1 = (*lines)[i].y1;
const float x2 = (*lines)[i].x2;
const float y2 = (*lines)[i].y2;
retval[i].p1[0] = x1;
retval[i].p1[1] = y1;
retval[i].p1[2] = 1.0f;
retval[i].p2[0] = x2;
retval[i].p2[1] = y2;
retval[i].p2[2] = 1.0f;
retval[i].length = sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
retval[i].width = 1.0f;
retval[i].weight = retval[i].length;
if ((*lines)[i].type == ControlLine::HORIZONTAL) {
retval[i].type = ASHIFT_LINE_HORIZONTAL_SELECTED;
} else if ((*lines)[i].type == ControlLine::VERTICAL) {
retval[i].type = ASHIFT_LINE_VERTICAL_SELECTED;
} else {
retval[i].type = ASHIFT_LINE_IRRELEVANT;
}
}
return retval;
}
} // namespace
PerspectiveCorrection::Params PerspectiveCorrection::autocompute(ImageSource *src, bool corr_pitch, bool corr_yaw, const procparams::ProcParams *pparams, const FramesMetaData *metadata, const std::vector<ControlLine> *control_lines)
{
auto pcp = procparams::PerspectiveParams(pparams->perspective);
procparams::PerspectiveParams dflt;
/*
pcp.horizontal = dflt.horizontal;
pcp.vertical = dflt.vertical;
pcp.angle = dflt.angle;
pcp.shear = dflt.shear;
*/
pcp.camera_pitch = dflt.camera_pitch;
pcp.camera_roll = dflt.camera_roll;
pcp.camera_yaw = dflt.camera_yaw;
dt_iop_ashift_params_t p;
dt_iop_ashift_gui_data_t g;
init_dt_structures(&p, &g, &pparams->perspective);
dt_iop_module_t module;
module.gui_data = &g;
module.is_raw = src->isRAW();
int tr = getCoarseBitMask(pparams->coarse);
int fw, fh;
src->getFullSize(fw, fh, tr);
if (control_lines == nullptr) {
int skip = max(float(max(fw, fh)) / 900.f + 0.5f, 1.f);
PreviewProps pp(0, 0, fw, fh, skip);
int w, h;
src->getSize(pp, w, h);
std::unique_ptr<Imagefloat> img(new Imagefloat(w, h));
ProcParams neutral;
neutral.raw.bayersensor.method = RAWParams::BayerSensor::getMethodString(RAWParams::BayerSensor::Method::FAST);
neutral.raw.xtranssensor.method = RAWParams::XTransSensor::getMethodString(RAWParams::XTransSensor::Method::FAST);
neutral.icm.outputProfile = ColorManagementParams::NoICMString;
src->getImage(src->getWB(), tr, img.get(), pp, neutral.toneCurve, neutral.raw);
src->convertColorSpace(img.get(), pparams->icm, src->getWB());
neutral.commonTrans.autofill = false; // Ensures crop factor is correct.
// TODO: Ensure image borders of rotated image do not get detected as lines.
neutral.rotate = pparams->rotate;
neutral.distortion = pparams->distortion;
neutral.distortion.defish = pparams->distortion.defish;
neutral.distortion.focal_length = pparams->distortion.focal_length;
neutral.perspective.camera_focal_length = pparams->perspective.camera_focal_length;
neutral.perspective.camera_crop_factor = pparams->perspective.camera_crop_factor;
neutral.perspective.method = pparams->perspective.method;
neutral.lensProf = pparams->lensProf;
ImProcFunctions ipf(&neutral, true);
if (ipf.needsTransform(w, h, src->getRotateDegree(), src->getMetaData())) {
Imagefloat *tmp = new Imagefloat(w, h);
ipf.transform(img.get(), tmp, 0, 0, 0, 0, w, h, w, h,
src->getMetaData(), src->getRotateDegree(), false);
img.reset(tmp);
}
// allocate the gui buffer
g.buf = static_cast<float *>(malloc(sizeof(float) * w * h * 4));
g.buf_width = w;
g.buf_height = h;
img->normalizeFloatTo1();
#ifdef _OPENMP
# pragma omp parallel for
#endif
for (int y = 0; y < h; ++y) {
for (int x = 0; x < w; ++x) {
int i = (y * w + x) * 4;
g.buf[i] = img->r(y, x);
g.buf[i+1] = img->g(y, x);
g.buf[i+2] = img->b(y, x);
g.buf[i+3] = 1.f;
}
}
}
dt_iop_ashift_fitaxis_t fitaxis = ASHIFT_FIT_NONE;
if (corr_pitch && corr_yaw) {
fitaxis = ASHIFT_FIT_BOTH_SHEAR;
} else if (corr_pitch) {
fitaxis = ASHIFT_FIT_VERTICALLY;
} else if (corr_yaw) {
fitaxis = ASHIFT_FIT_HORIZONTALLY;
}
// reset the pseudo-random seed for repeatability -- ashift_dt uses rand()
// internally!
srand(1);
bool res;
if (control_lines == nullptr) {
res = do_get_structure(&module, &p, ASHIFT_ENHANCE_EDGES) && do_fit(&module, &p, fitaxis);
} else {
std::unique_ptr<dt_iop_ashift_line_t[]> ashift_lines = toAshiftLines(control_lines);
dt_iop_ashift_gui_data_t *g = module.gui_data;
g->lines_count = control_lines->size();
g->lines = ashift_lines.get();
g->lines_in_height = fh;
g->lines_in_width = fw;
update_lines_count(g->lines, g->lines_count, &(g->vertical_count), &(g->horizontal_count));
res = do_fit(&module, &p, fitaxis, 2);
g->lines = nullptr;
}
Params retval = {
.angle = p.rotation,
.pitch = p.camera_pitch,
.yaw = p.camera_yaw
};
// cleanup the gui
if (g.lines) free(g.lines);
if (g.points) free(g.points);
if (g.points_idx) free(g.points_idx);
if (g.buf) free(g.buf);
if (!res) {
retval.angle = pparams->perspective.camera_roll;
retval.pitch = pparams->perspective.camera_pitch;
retval.yaw = pparams->perspective.camera_yaw;
}
return retval;
}
/*
void PerspectiveCorrection::autocrop(int width, int height, bool fixratio, const procparams::PerspectiveParams &params, const FramesMetaData *metadata, int &x, int &y, int &w, int &h)
{
auto pp = import_meta(params, metadata);
double cw, ch;
get_view_size(width, height, params, cw, ch);
double s = min(double(width)/cw, double(height)/ch);
dt_iop_ashift_params_t p;
dt_iop_ashift_gui_data_t g;
init_dt_structures(&p, &g, &pp);
dt_iop_module_t module = { &g, false };
g.buf_width = width;
g.buf_height = height;
p.cropmode = fixratio ? ASHIFT_CROP_ASPECT : ASHIFT_CROP_LARGEST;
do_crop(&module, &p);
cw *= s;
ch *= s;
double ox = p.cl * cw;
double oy = p.ct * ch;
x = ox - (cw - width)/2.0 + 0.5;
y = oy - (ch - height)/2.0 + 0.5;
w = (p.cr - p.cl) * cw;
h = (p.cb - p.ct) * ch;
}
*/
} // namespace rtengine