/* -*- C++ -*- * * This file is part of RawTherapee. * * Copyright (c) 2019 Alberto Griggio * * 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 . */ // 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 . */ // 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 #include #include #include #include #include #include #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, 3> tmpsrc; std::array, 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 get_corners(int w, int h) { int x1 = 0, y1 = 0; int x2 = w, y2 = h; std::vector 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 ¶ms, 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 toAshiftLines(const std::vector *lines) { std::unique_ptr 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 *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 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.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(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 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 ¶ms, 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