Introduces "long edge" and "short edge" options to resize an image. The GUI is made such that the relevant spinboxes only appear for the selected option. Unrelated values (e.g. for box-mode) are not updated.
462 lines
13 KiB
C++
462 lines
13 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 "improcfun.h"
|
|
|
|
#include "alignedbuffer.h"
|
|
#include "imagefloat.h"
|
|
#include "labimage.h"
|
|
#include "opthelper.h"
|
|
#include "rt_math.h"
|
|
#include "procparams.h"
|
|
#include "sleef.h"
|
|
|
|
//#define PROFILE
|
|
|
|
#ifdef PROFILE
|
|
# include <iostream>
|
|
#endif
|
|
|
|
|
|
namespace rtengine
|
|
{
|
|
|
|
static inline float Lanc (float x, float a)
|
|
{
|
|
if (x * x < 1e-6f) {
|
|
return 1.0f;
|
|
} else if (x * x > a * a) {
|
|
return 0.0f;
|
|
} else {
|
|
x = static_cast<float> (rtengine::RT_PI) * x;
|
|
return a * xsinf (x) * xsinf (x / a) / (x * x);
|
|
}
|
|
}
|
|
|
|
void ImProcFunctions::Lanczos (const Imagefloat* src, Imagefloat* dst, float scale)
|
|
{
|
|
|
|
const float delta = 1.0f / scale;
|
|
const float a = 3.0f;
|
|
const float sc = min (scale, 1.0f);
|
|
const int support = static_cast<int> (2.0f * a / sc) + 1;
|
|
|
|
#ifdef _OPENMP
|
|
#pragma omp parallel
|
|
#endif
|
|
{
|
|
// storage for precomputed parameters for horisontal interpolation
|
|
float * wwh = new float[support * dst->getWidth()];
|
|
int * jj0 = new int[dst->getWidth()];
|
|
int * jj1 = new int[dst->getWidth()];
|
|
|
|
// temporal storage for vertically-interpolated row of pixels
|
|
float * lr = new float[src->getWidth()];
|
|
float * lg = new float[src->getWidth()];
|
|
float * lb = new float[src->getWidth()];
|
|
|
|
// Phase 1: precompute coefficients for horisontal interpolation
|
|
|
|
for (int j = 0; j < dst->getWidth(); j++) {
|
|
|
|
// x coord of the center of pixel on src image
|
|
float x0 = (static_cast<float> (j) + 0.5f) * delta - 0.5f;
|
|
|
|
// weights for interpolation in horisontal direction
|
|
float * w = wwh + j * support;
|
|
|
|
// sum of weights used for normalization
|
|
float ws = 0.0f;
|
|
|
|
jj0[j] = max (0, static_cast<int> (floorf (x0 - a / sc)) + 1);
|
|
jj1[j] = min (src->getWidth(), static_cast<int> (floorf (x0 + a / sc)) + 1);
|
|
|
|
// calculate weights
|
|
for (int jj = jj0[j]; jj < jj1[j]; jj++) {
|
|
int k = jj - jj0[j];
|
|
float z = sc * (x0 - static_cast<float> (jj));
|
|
w[k] = Lanc (z, a);
|
|
ws += w[k];
|
|
}
|
|
|
|
// normalize weights
|
|
for (int k = 0; k < support; k++) {
|
|
w[k] /= ws;
|
|
}
|
|
}
|
|
|
|
// Phase 2: do actual interpolation
|
|
#ifdef _OPENMP
|
|
#pragma omp for
|
|
#endif
|
|
|
|
for (int i = 0; i < dst->getHeight(); i++) {
|
|
|
|
// y coord of the center of pixel on src image
|
|
float y0 = (static_cast<float> (i) + 0.5f) * delta - 0.5f;
|
|
|
|
// weights for interpolation in y direction
|
|
float w[support];
|
|
for (auto& f : w) {
|
|
f = 0.f;
|
|
}
|
|
|
|
// sum of weights used for normalization
|
|
float ws = 0.0f;
|
|
|
|
int ii0 = max (0, static_cast<int> (floorf (y0 - a / sc)) + 1);
|
|
int ii1 = min (src->getHeight(), static_cast<int> (floorf (y0 + a / sc)) + 1);
|
|
|
|
// calculate weights for vertical interpolation
|
|
for (int ii = ii0; ii < ii1; ii++) {
|
|
int k = ii - ii0;
|
|
float z = sc * (y0 - static_cast<float> (ii));
|
|
w[k] = Lanc (z, a);
|
|
ws += w[k];
|
|
}
|
|
|
|
// normalize weights
|
|
for (int k = 0; k < support; k++) {
|
|
w[k] /= ws;
|
|
}
|
|
|
|
// Do vertical interpolation. Store results.
|
|
for (int j = 0; j < src->getWidth(); j++) {
|
|
|
|
float r = 0.0f, g = 0.0f, b = 0.0f;
|
|
|
|
for (int ii = ii0; ii < ii1; ii++) {
|
|
int k = ii - ii0;
|
|
|
|
r += w[k] * src->r (ii, j);
|
|
g += w[k] * src->g (ii, j);
|
|
b += w[k] * src->b (ii, j);
|
|
}
|
|
|
|
lr[j] = r;
|
|
lg[j] = g;
|
|
lb[j] = b;
|
|
}
|
|
|
|
// Do horizontal interpolation
|
|
for (int j = 0; j < dst->getWidth(); j++) {
|
|
|
|
float * wh = wwh + support * j;
|
|
|
|
float r = 0.0f, g = 0.0f, b = 0.0f;
|
|
|
|
for (int jj = jj0[j]; jj < jj1[j]; jj++) {
|
|
int k = jj - jj0[j];
|
|
|
|
r += wh[k] * lr[jj];
|
|
g += wh[k] * lg[jj];
|
|
b += wh[k] * lb[jj];
|
|
}
|
|
|
|
dst->r (i, j) = /*CLIP*/ (r);//static_cast<int> (r));
|
|
dst->g (i, j) = /*CLIP*/ (g);//static_cast<int> (g));
|
|
dst->b (i, j) = /*CLIP*/ (b);//static_cast<int> (b));
|
|
}
|
|
}
|
|
|
|
delete[] wwh;
|
|
delete[] jj0;
|
|
delete[] jj1;
|
|
delete[] lr;
|
|
delete[] lg;
|
|
delete[] lb;
|
|
}
|
|
}
|
|
|
|
|
|
void ImProcFunctions::Lanczos (const LabImage* src, LabImage* dst, float scale)
|
|
{
|
|
const float delta = 1.0f / scale;
|
|
constexpr float a = 3.0f;
|
|
const float sc = min(scale, 1.0f);
|
|
const int support = static_cast<int> (2.0f * a / sc) + 1;
|
|
|
|
// storage for precomputed parameters for horizontal interpolation
|
|
float* const wwh = new float[support * dst->W];
|
|
int* const jj0 = new int[dst->W];
|
|
int* const jj1 = new int[dst->W];
|
|
|
|
// Phase 1: precompute coefficients for horizontal interpolation
|
|
for (int j = 0; j < dst->W; j++) {
|
|
|
|
// x coord of the center of pixel on src image
|
|
float x0 = (static_cast<float> (j) + 0.5f) * delta - 0.5f;
|
|
|
|
// weights for interpolation in horizontal direction
|
|
float * w = wwh + j * support;
|
|
|
|
// sum of weights used for normalization
|
|
float ws = 0.0f;
|
|
|
|
jj0[j] = max (0, static_cast<int> (floorf (x0 - a / sc)) + 1);
|
|
jj1[j] = min (src->W, static_cast<int> (floorf (x0 + a / sc)) + 1);
|
|
|
|
// calculate weights
|
|
for (int jj = jj0[j]; jj < jj1[j]; jj++) {
|
|
int k = jj - jj0[j];
|
|
float z = sc * (x0 - static_cast<float> (jj));
|
|
w[k] = Lanc (z, a);
|
|
ws += w[k];
|
|
}
|
|
|
|
// normalize weights
|
|
for (int k = 0; k < support; k++) {
|
|
w[k] /= ws;
|
|
}
|
|
}
|
|
|
|
#ifdef _OPENMP
|
|
#pragma omp parallel
|
|
#endif
|
|
{
|
|
// temporal storage for vertically-interpolated row of pixels
|
|
AlignedBuffer<float> aligned_buffer_ll(src->W);
|
|
AlignedBuffer<float> aligned_buffer_la(src->W);
|
|
AlignedBuffer<float> aligned_buffer_lb(src->W);
|
|
float* const lL = aligned_buffer_ll.data;
|
|
float* const la = aligned_buffer_la.data;
|
|
float* const lb = aligned_buffer_lb.data;
|
|
// weights for interpolation in y direction
|
|
float w[support] ALIGNED64;
|
|
memset(w, 0, sizeof(w));
|
|
|
|
// Phase 2: do actual interpolation
|
|
#ifdef _OPENMP
|
|
#pragma omp for
|
|
#endif
|
|
|
|
for (int i = 0; i < dst->H; i++) {
|
|
// y coord of the center of pixel on src image
|
|
float y0 = (static_cast<float> (i) + 0.5f) * delta - 0.5f;
|
|
|
|
// sum of weights used for normalization
|
|
float ws = 0.0f;
|
|
|
|
int ii0 = max (0, static_cast<int> (floorf (y0 - a / sc)) + 1);
|
|
int ii1 = min (src->H, static_cast<int> (floorf (y0 + a / sc)) + 1);
|
|
|
|
// calculate weights for vertical interpolation
|
|
for (int ii = ii0; ii < ii1; ii++) {
|
|
int k = ii - ii0;
|
|
float z = sc * (y0 - static_cast<float> (ii));
|
|
w[k] = Lanc (z, a);
|
|
ws += w[k];
|
|
}
|
|
|
|
// normalize weights
|
|
for (int k = 0; k < support; k++) {
|
|
w[k] /= ws;
|
|
}
|
|
|
|
// Do vertical interpolation. Store results.
|
|
int j = 0;
|
|
#ifdef __SSE2__
|
|
__m128 Lv, av, bv, wkv;
|
|
|
|
for (j = 0; j < src->W - 3; j += 4) {
|
|
Lv = ZEROV;
|
|
av = ZEROV;
|
|
bv = ZEROV;
|
|
|
|
for (int ii = ii0; ii < ii1; ii++) {
|
|
int k = ii - ii0;
|
|
wkv = F2V(w[k]);
|
|
Lv += wkv * LVFU(src->L[ii][j]);
|
|
av += wkv * LVFU(src->a[ii][j]);
|
|
bv += wkv * LVFU(src->b[ii][j]);
|
|
}
|
|
|
|
STVF(lL[j], Lv);
|
|
STVF(la[j], av);
|
|
STVF(lb[j], bv);
|
|
}
|
|
#endif
|
|
|
|
for (; j < src->W; ++j) {
|
|
float Ll = 0.0f, La = 0.0f, Lb = 0.0f;
|
|
|
|
for (int ii = ii0; ii < ii1; ++ii) {
|
|
int k = ii - ii0;
|
|
|
|
Ll += w[k] * src->L[ii][j];
|
|
La += w[k] * src->a[ii][j];
|
|
Lb += w[k] * src->b[ii][j];
|
|
}
|
|
|
|
lL[j] = Ll;
|
|
la[j] = La;
|
|
lb[j] = Lb;
|
|
}
|
|
|
|
// Do horizontal interpolation
|
|
for (int x = 0; x < dst->W; ++x) {
|
|
float * wh = wwh + support * x;
|
|
float Ll = 0.0f, La = 0.0f, Lb = 0.0f;
|
|
|
|
for (int jj = jj0[x]; jj < jj1[x]; ++jj) {
|
|
int k = jj - jj0[x];
|
|
|
|
Ll += wh[k] * lL[jj];
|
|
La += wh[k] * la[jj];
|
|
Lb += wh[k] * lb[jj];
|
|
}
|
|
|
|
dst->L[i][x] = Ll;
|
|
dst->a[i][x] = La;
|
|
dst->b[i][x] = Lb;
|
|
}
|
|
}
|
|
}
|
|
delete[] jj0;
|
|
delete[] jj1;
|
|
delete[] wwh;
|
|
}
|
|
|
|
float ImProcFunctions::resizeScale (const ProcParams* params, int fw, int fh, int &imw, int &imh)
|
|
{
|
|
imw = fw;
|
|
imh = fh;
|
|
|
|
if (!params || !params->resize.enabled) {
|
|
return 1.0;
|
|
}
|
|
|
|
// get the resize parameters
|
|
int refw, refh;
|
|
double dScale;
|
|
|
|
if (params->crop.enabled && params->resize.appliesTo == "Cropped area") {
|
|
// the resize values applies to the crop dimensions
|
|
refw = params->crop.w;
|
|
refh = params->crop.h;
|
|
} else {
|
|
// the resize values applies to the image dimensions
|
|
// if a crop exists, it will be resized to the calculated scale
|
|
refw = fw;
|
|
refh = fh;
|
|
}
|
|
|
|
switch (params->resize.dataspec) {
|
|
case (1):
|
|
// Width
|
|
dScale = (double)params->resize.width / (double)refw;
|
|
break;
|
|
|
|
case (2):
|
|
// Height
|
|
dScale = (double)params->resize.height / (double)refh;
|
|
break;
|
|
|
|
case (3):
|
|
|
|
// FitBox
|
|
if ((double)refw / (double)refh > (double)params->resize.width / (double)params->resize.height) {
|
|
dScale = (double)params->resize.width / (double)refw;
|
|
} else {
|
|
dScale = (double)params->resize.height / (double)refh;
|
|
}
|
|
dScale = (dScale > 1.0 && !params->resize.allowUpscaling) ? 1.0 : dScale;
|
|
|
|
break;
|
|
|
|
case (4):
|
|
|
|
// Long Edge
|
|
if (refw > refh) {
|
|
dScale = (double)params->resize.longedge / (double)refw;
|
|
} else {
|
|
dScale = (double)params->resize.longedge / (double)refh;
|
|
}
|
|
break;
|
|
|
|
case (5):
|
|
|
|
// Short Edge
|
|
if (refw > refh) {
|
|
dScale = (double)params->resize.shortedge / (double)refh;
|
|
} else {
|
|
dScale = (double)params->resize.shortedge / (double)refw;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
// Scale
|
|
dScale = params->resize.scale;
|
|
break;
|
|
}
|
|
|
|
if (fabs (dScale - 1.0) <= 1e-5) {
|
|
return 1.0;
|
|
}
|
|
|
|
if (params->crop.enabled && params->resize.appliesTo == "Full image") {
|
|
imw = params->crop.w;
|
|
imh = params->crop.h;
|
|
} else {
|
|
imw = refw;
|
|
imh = refh;
|
|
}
|
|
|
|
imw = (int) ( (double)imw * dScale + 0.5 );
|
|
imh = (int) ( (double)imh * dScale + 0.5 );
|
|
return (float)dScale;
|
|
}
|
|
|
|
void ImProcFunctions::resize (Imagefloat* src, Imagefloat* dst, float dScale)
|
|
{
|
|
#ifdef PROFILE
|
|
time_t t1 = clock();
|
|
#endif
|
|
|
|
if (params->resize.method != "Nearest" ) {
|
|
Lanczos (src, dst, dScale);
|
|
} else {
|
|
// Nearest neighbour algorithm
|
|
#ifdef _OPENMP
|
|
#pragma omp parallel for if (multiThread)
|
|
#endif
|
|
|
|
for (int i = 0; i < dst->getHeight(); i++) {
|
|
int sy = i / dScale;
|
|
sy = LIM (sy, 0, src->getHeight() - 1);
|
|
|
|
for (int j = 0; j < dst->getWidth(); j++) {
|
|
int sx = j / dScale;
|
|
sx = LIM (sx, 0, src->getWidth() - 1);
|
|
dst->r (i, j) = src->r (sy, sx);
|
|
dst->g (i, j) = src->g (sy, sx);
|
|
dst->b (i, j) = src->b (sy, sx);
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef PROFILE
|
|
time_t t2 = clock();
|
|
std::cout << "Resize: " << params->resize.method << ": "
|
|
<< (float) (t2 - t1) / CLOCKS_PER_SEC << std::endl;
|
|
#endif
|
|
}
|
|
|
|
}
|