* Added support for non-raw files to the film negative tool. The tool is now under the "Color" tab. Moved the entire filmneg code downstream, right after input profile conversion. Usage changes a bit: White Balance must be set to the _backlight_ color temperature. Added two more sliders to color-balance the picture after negative inversion. Legacy inversion method kept for compatibility with processing profiles saved by RT v5.7 or 5.8 (only if Film Negative was enabled). Should be removed in a future version. There is still an issue with DCP profiles that need the look table to be active to work properly. Using a simple matrix input profile (or just camera standard) is recommended for now. * The user can now choose to perform inversion before or after input colorspace conversion. Seamless backwards compatibility with previous processing profiles; upgrading from a previous version now gives an (almost) identical output as before. Generalised the concept of film base values: the processing profile now contains a pair of RGB triplets, "reference input" and "reference output", which makes it much more straightforward to compute the output multipliers. * Added support for `RGB` data type to putToKeyFile, removed the now unused `RGB::toVector()` method. Some cleanup. * Spot balance picker now stays active indefinitely. Can be disabled by right-clicking, too. * Removed film negative from `filterRawRefresh` condition, since the new version does not require raw rendering anymore. * The Output Level slider is now exponential, so it should feel more familiar to use (similar to the exposure compensation slider). * Removed old `RedBase`, `GreenBase`, `BlueBase` keys from the PP3 file after params upgrade. Keys are only kept if the file only undergoes batch edit (hence no params upgrade was done). * Made the balance sliders exponential and centered at zero. Now they should be a bit smoother and possibly more user-friendly. * Changed adjusters' step to more useful values, they were too fine-grained and hard to adjust using the +/- spinbutton. Increased max ratio value from 3 to 5, as i found an old negative needing a very high blue channel exponent. * Added an initial processing profile for film negative inversion, to be provided as a bundled profile. * Removed Output Level and balance sliders when in batch mode: since the effect of these sliders is dependent on the reference input values, those values needed to be copied as well. And, touching any of the sliders needed to flag all three as dirty. All this felt more confusing than useful. It should be sufficient (and clearer) to copy/paste the params, and then fine-tune the balance on individual pictures when needed. * Set bayer demosaic method to RCD in the bundled "Film Negative" processing profile. RCD seems to play a bit better with Capture Sharpening in the presence of film grain, compared to Amaze. This will favor new users starting with all-defaults settings, hence having Capture Sharpening enabled by default. * Removed incorrect "contrast" term from the "Reference exponent" label. This parameter adjusts the image _gamma_, not its contrast.
668 lines
21 KiB
C++
668 lines
21 KiB
C++
/*
|
|
* This file is part of RawTherapee.
|
|
*
|
|
* Copyright (c) 2019 Alberto Romei <aldrop8@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 <https://www.gnu.org/licenses/>.
|
|
*/
|
|
#include <cmath>
|
|
#include <iostream>
|
|
|
|
#include "rawimage.h"
|
|
#include "rawimagesource.h"
|
|
#include "improcfun.h"
|
|
#include "improccoordinator.h"
|
|
#include "imagefloat.h"
|
|
|
|
#include "coord.h"
|
|
#include "opthelper.h"
|
|
#include "pixelsmap.h"
|
|
#include "procparams.h"
|
|
#include "rt_algo.h"
|
|
#include "rtengine.h"
|
|
#include "rtthumbnail.h"
|
|
#include "sleef.h"
|
|
//#define BENCHMARK
|
|
//#include "StopWatch.h"
|
|
#include "iccstore.h"
|
|
#include "rt_math.h"
|
|
#include "color.h"
|
|
|
|
|
|
namespace
|
|
{
|
|
using rtengine::settings;
|
|
using rtengine::Coord2D;
|
|
using rtengine::ColorTemp;
|
|
using rtengine::findMinMaxPercentile;
|
|
using rtengine::ImageSource;
|
|
using rtengine::Imagefloat;
|
|
using rtengine::procparams::FilmNegativeParams;
|
|
using rtengine::procparams::ColorManagementParams;
|
|
using rtengine::procparams::RAWParams;
|
|
using rtengine::ICCStore;
|
|
using rtengine::MAXVALF;
|
|
using rtengine::CLIP;
|
|
using rtengine::TMatrix;
|
|
using rtengine::Color;
|
|
using RGB = rtengine::procparams::FilmNegativeParams::RGB;
|
|
|
|
Coord2D translateCoord(const rtengine::ImProcFunctions& ipf, int fw, int fh, int x, int y)
|
|
{
|
|
|
|
const std::vector<Coord2D> points = {Coord2D(x, y)};
|
|
|
|
std::vector<Coord2D> red;
|
|
std::vector<Coord2D> green;
|
|
std::vector<Coord2D> blue;
|
|
ipf.transCoord(fw, fh, points, red, green, blue);
|
|
|
|
return green[0];
|
|
}
|
|
|
|
|
|
void getSpotAvgMax(ImageSource *imgsrc, ColorTemp currWB, const std::unique_ptr<rtengine::procparams::ProcParams> ¶ms,
|
|
Coord2D p, int tr, int spotSize, RGB &avg, RGB &max)
|
|
{
|
|
int x1 = MAX(0, (int)p.x - spotSize / 2);
|
|
int y1 = MAX(0, (int)p.y - spotSize / 2);
|
|
PreviewProps pp(x1, y1, spotSize, spotSize, 1);
|
|
|
|
if (settings->verbose) {
|
|
printf("Spot: %d,%d %d,%d\n", x1, y1, x1 + spotSize / 2, y1 + spotSize / 2);
|
|
}
|
|
|
|
rtengine::Imagefloat spotImg(spotSize, spotSize);
|
|
imgsrc->getImage(currWB, tr, &spotImg, pp, params->toneCurve, params->raw);
|
|
|
|
auto avgMax = [spotSize, &spotImg](RGB & avg, RGB & max) -> void {
|
|
avg = {};
|
|
max = {};
|
|
|
|
for (int i = 0; i < spotSize; ++i)
|
|
{
|
|
for (int j = 0; j < spotSize; ++j) {
|
|
|
|
float r = spotImg.r(i, j);
|
|
float g = spotImg.g(i, j);
|
|
float b = spotImg.b(i, j);
|
|
|
|
avg.r += r;
|
|
avg.g += g;
|
|
avg.b += b;
|
|
max.r = MAX(max.r, r);
|
|
max.g = MAX(max.g, g);
|
|
max.b = MAX(max.b, b);
|
|
}
|
|
}
|
|
|
|
avg.r /= (spotSize * spotSize);
|
|
avg.g /= (spotSize * spotSize);
|
|
avg.b /= (spotSize * spotSize);
|
|
};
|
|
|
|
if (params->filmNegative.colorSpace == rtengine::FilmNegativeParams::ColorSpace::INPUT) {
|
|
avgMax(avg, max);
|
|
} else {
|
|
// Convert spot image to current working space
|
|
imgsrc->convertColorSpace(&spotImg, params->icm, currWB);
|
|
|
|
avgMax(avg, max);
|
|
|
|
// TODO handle custom color profile !
|
|
}
|
|
|
|
// Clip away zeroes or negative numbers, you never know
|
|
avg.r = MAX(avg.r, 1.f);
|
|
avg.g = MAX(avg.g, 1.f);
|
|
avg.b = MAX(avg.b, 1.f);
|
|
|
|
if (settings->verbose) {
|
|
printf("Average Spot RGB: %f,%f,%f\n", avg.r, avg.g, avg.b);
|
|
}
|
|
}
|
|
|
|
|
|
void calcMedians(
|
|
const rtengine::Imagefloat* input,
|
|
int x1, int y1, int x2, int y2,
|
|
float &rmed, float &gmed, float &bmed
|
|
)
|
|
{
|
|
using rtengine::findMinMaxPercentile;
|
|
|
|
// Channel vectors to calculate medians
|
|
std::vector<float> rv, gv, bv;
|
|
|
|
const int sz = (x2 - x1) * (y2 - y1);
|
|
rv.reserve(sz);
|
|
gv.reserve(sz);
|
|
bv.reserve(sz);
|
|
|
|
|
|
for (int ii = y1; ii < y2; ii ++) {
|
|
for (int jj = x1; jj < x2; jj ++) {
|
|
rv.push_back(input->r(ii, jj));
|
|
gv.push_back(input->g(ii, jj));
|
|
bv.push_back(input->b(ii, jj));
|
|
}
|
|
}
|
|
|
|
// Calculate channel medians of the specified area
|
|
findMinMaxPercentile(rv.data(), rv.size(), 0.5f, rmed, 0.5f, rmed, true);
|
|
findMinMaxPercentile(gv.data(), gv.size(), 0.5f, gmed, 0.5f, gmed, true);
|
|
findMinMaxPercentile(bv.data(), bv.size(), 0.5f, bmed, 0.5f, bmed, true);
|
|
}
|
|
|
|
|
|
RGB getMedians(const rtengine::Imagefloat* input, int borderPercent)
|
|
{
|
|
float rmed, gmed, bmed;
|
|
// Cut 20% border from medians calculation. It will probably contain outlier values
|
|
// from the film holder, which will bias the median result.
|
|
const int bW = input->getWidth() * borderPercent / 100;
|
|
const int bH = input->getHeight() * borderPercent / 100;
|
|
calcMedians(input, bW, bH,
|
|
input->getWidth() - bW, input->getHeight() - bH,
|
|
rmed, gmed, bmed);
|
|
|
|
if (settings->verbose) {
|
|
printf("Channel medians: R=%g, G=%g, B=%g\n", rmed, gmed, bmed);
|
|
}
|
|
|
|
return { rmed, gmed, bmed };
|
|
}
|
|
|
|
/*
|
|
// TODO not needed for now
|
|
void convertColorSpace(Imagefloat* input, const TMatrix &src2xyz, const TMatrix &xyz2dest)
|
|
{
|
|
|
|
#ifdef _OPENMP
|
|
#pragma omp parallel for
|
|
#endif
|
|
|
|
for (int i = 0; i < input->getHeight(); i++) {
|
|
for (int j = 0; j < input->getWidth(); j++) {
|
|
|
|
float newr = input->r(i, j);
|
|
float newg = input->g(i, j);
|
|
float newb = input->b(i, j);
|
|
|
|
float x, y, z;
|
|
Color::rgbxyz (newr, newg, newb, x, y, z, src2xyz);
|
|
Color::xyz2rgb (x, y, z, newr, newg, newb, xyz2dest);
|
|
|
|
input->r(i, j) = newr;
|
|
input->g(i, j) = newg;
|
|
input->b(i, j) = newb;
|
|
|
|
}
|
|
}
|
|
}
|
|
*/
|
|
|
|
|
|
/**
|
|
* Perform actual film negative inversion process.
|
|
* Returns true if the input and output reference values are not set in params; refIn/refOut will be updated with median-based estimates.
|
|
* Otherwise, use provided values in params and return false
|
|
*/
|
|
bool doProcess(Imagefloat *input, Imagefloat *output,
|
|
const FilmNegativeParams ¶ms, const ColorManagementParams &icmParams,
|
|
RGB &refIn, RGB &refOut)
|
|
{
|
|
bool refsUpdated = false;
|
|
|
|
float rexp = -(params.greenExp * params.redRatio);
|
|
float gexp = -params.greenExp;
|
|
float bexp = -(params.greenExp * params.blueRatio);
|
|
|
|
// In case we are processing a thumbnail, reference values might not be set in params,
|
|
// so make an estimate on the fly, using channel medians
|
|
if (refIn.g <= 0.f) {
|
|
// Calc medians, 20% border cut
|
|
refIn = getMedians(input, 20);
|
|
refsUpdated = true;
|
|
} else {
|
|
refIn = params.refInput;
|
|
}
|
|
|
|
if (refOut.g <= 0.f) {
|
|
// Median will correspond to gray, 1/24th of max in the output
|
|
refOut = { MAXVALF / 24.f, MAXVALF / 24.f, MAXVALF / 24.f };
|
|
refsUpdated = true;
|
|
} else {
|
|
refOut = params.refOutput;
|
|
}
|
|
|
|
// Apply channel exponents to reference input values, and compute suitable multipliers
|
|
// in order to reach reference output values.
|
|
float rmult = refOut.r / pow_F(rtengine::max(refIn.r, 1.f), rexp);
|
|
float gmult = refOut.g / pow_F(rtengine::max(refIn.g, 1.f), gexp);
|
|
float bmult = refOut.b / pow_F(rtengine::max(refIn.b, 1.f), bexp);
|
|
|
|
|
|
#ifdef __SSE2__
|
|
const vfloat clipv = F2V(MAXVALF);
|
|
const vfloat rexpv = F2V(rexp);
|
|
const vfloat gexpv = F2V(gexp);
|
|
const vfloat bexpv = F2V(bexp);
|
|
const vfloat rmultv = F2V(rmult);
|
|
const vfloat gmultv = F2V(gmult);
|
|
const vfloat bmultv = F2V(bmult);
|
|
#endif
|
|
|
|
const int rheight = input->getHeight();
|
|
const int rwidth = input->getWidth();
|
|
|
|
for (int i = 0; i < rheight; i++) {
|
|
float *rlinein = input->r(i);
|
|
float *glinein = input->g(i);
|
|
float *blinein = input->b(i);
|
|
float *rlineout = output->r(i);
|
|
float *glineout = output->g(i);
|
|
float *blineout = output->b(i);
|
|
int j = 0;
|
|
#ifdef __SSE2__
|
|
|
|
for (; j < rwidth - 3; j += 4) {
|
|
STVFU(rlineout[j], vminf(rmultv * pow_F(LVFU(rlinein[j]), rexpv), clipv));
|
|
STVFU(glineout[j], vminf(gmultv * pow_F(LVFU(glinein[j]), gexpv), clipv));
|
|
STVFU(blineout[j], vminf(bmultv * pow_F(LVFU(blinein[j]), bexpv), clipv));
|
|
}
|
|
|
|
#endif
|
|
|
|
for (; j < rwidth; ++j) {
|
|
rlineout[j] = CLIP(rmult * pow_F(rlinein[j], rexp));
|
|
glineout[j] = CLIP(gmult * pow_F(glinein[j], gexp));
|
|
blineout[j] = CLIP(bmult * pow_F(blinein[j], bexp));
|
|
}
|
|
}
|
|
|
|
return refsUpdated;
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
bool rtengine::ImProcFunctions::filmNegativeProcess(
|
|
Imagefloat *input, Imagefloat *output, FilmNegativeParams &fnp,
|
|
const RAWParams &rawParams, const ImageSource* imgsrc, const ColorTemp &currWB)
|
|
{
|
|
//BENCHFUNMICRO
|
|
|
|
if (!fnp.enabled) {
|
|
return false;
|
|
}
|
|
|
|
bool paramsUpdated = false;
|
|
|
|
RGB &refIn = fnp.refInput;
|
|
RGB &refOut = fnp.refOutput;
|
|
|
|
// If we're opening a profile from an older version, apply the proper multiplier
|
|
// compensations to make processing backwards compatible.
|
|
|
|
if (fnp.backCompat == FilmNegativeParams::BackCompat::V1) {
|
|
// Calc medians, no border cut, compensate currWB in+out
|
|
refIn = getMedians(input, 0);
|
|
refOut = { MAXVALF / 24.f, MAXVALF / 24.f, MAXVALF / 24.f };
|
|
|
|
std::array<float, 4> scale_mul = { 1.f, 1.f, 1.f, 1.f };
|
|
float autoGainComp, rm, gm, bm;
|
|
imgsrc->getWBMults(currWB, params->raw, scale_mul, autoGainComp, rm, gm, bm);
|
|
|
|
refOut.r *= rm;
|
|
refOut.g *= gm;
|
|
refOut.b *= bm;
|
|
|
|
paramsUpdated = true;
|
|
|
|
} else if (fnp.backCompat == FilmNegativeParams::BackCompat::V2) {
|
|
|
|
std::array<float, 4> scale_mul = { 1.f, 1.f, 1.f, 1.f };
|
|
float autoGainComp, rm, gm, bm;
|
|
imgsrc->getWBMults(currWB, params->raw, scale_mul, autoGainComp, rm, gm, bm);
|
|
|
|
float rm2, gm2, bm2;
|
|
imgsrc->getWBMults(rtengine::ColorTemp(3500., 1., 1., "Custom"), params->raw, scale_mul, autoGainComp, rm2, gm2, bm2);
|
|
float mg = rtengine::max(rm2, gm2, bm2);
|
|
rm2 /= mg;
|
|
gm2 /= mg;
|
|
bm2 /= mg;
|
|
|
|
if (fnp.refInput.g == 0.f) {
|
|
// Calc medians, 20% border cut
|
|
refIn = getMedians(input, 20);
|
|
refOut = { MAXVALF / 24.f, MAXVALF / 24.f, MAXVALF / 24.f };
|
|
} else if (fnp.refInput.g > 0.f) {
|
|
// Calc refInput + refOutput from base levels, compensate currWB in, 3500 out
|
|
refIn = fnp.refInput;
|
|
refIn.r *= rm * scale_mul[0];
|
|
refIn.g *= gm * scale_mul[1];
|
|
refIn.b *= bm * scale_mul[2];
|
|
refOut = { MAXVALF / 512.f, MAXVALF / 512.f, MAXVALF / 512.f };
|
|
}
|
|
|
|
refOut.r *= rm * autoGainComp / rm2;
|
|
refOut.g *= gm * autoGainComp / gm2;
|
|
refOut.b *= bm * autoGainComp / bm2;
|
|
|
|
paramsUpdated = true;
|
|
|
|
}
|
|
|
|
if (settings->verbose && fnp.backCompat != FilmNegativeParams::BackCompat::CURRENT) {
|
|
printf("Upgraded from V%d - refIn: R=%g G=%g B=%g refOut: R=%g G=%g B=%g\n",
|
|
(int)fnp.backCompat,
|
|
static_cast<double>(refIn.r), static_cast<double>(refIn.g), static_cast<double>(refIn.b),
|
|
static_cast<double>(refOut.r), static_cast<double>(refOut.g), static_cast<double>(refOut.b));
|
|
}
|
|
|
|
// FilmNeg params are now upgraded to the latest version
|
|
fnp.backCompat = FilmNegativeParams::BackCompat::CURRENT;
|
|
|
|
// Perform actual inversion. Return true if reference values are computed from medians
|
|
paramsUpdated |= doProcess(input, output, fnp, this->params->icm, refIn, refOut);
|
|
|
|
return paramsUpdated;
|
|
|
|
}
|
|
|
|
void rtengine::ImProcFunctions::filmNegativeProcess(rtengine::Imagefloat *input, rtengine::Imagefloat *output,
|
|
const procparams::FilmNegativeParams ¶ms)
|
|
{
|
|
//BENCHFUNMICRO
|
|
|
|
if (!params.enabled) {
|
|
return;
|
|
}
|
|
|
|
RGB refIn = params.refInput, refOut = params.refOutput;
|
|
|
|
doProcess(input, output, params, this->params->icm, refIn, refOut);
|
|
|
|
}
|
|
|
|
|
|
bool rtengine::ImProcCoordinator::getFilmNegativeSpot(int x, int y, const int spotSize, RGB &refInput, RGB &refOutput)
|
|
{
|
|
MyMutex::MyLock lock(mProcessing);
|
|
|
|
const int tr = getCoarseBitMask(params->coarse);
|
|
|
|
const Coord2D p = translateCoord(ipf, fw, fh, x, y);
|
|
|
|
// Get the average channel values from the sampled spot
|
|
RGB avg, max;
|
|
getSpotAvgMax(imgsrc, currWB, params, p, tr, spotSize, avg, max);
|
|
|
|
float rexp = -(params->filmNegative.greenExp * params->filmNegative.redRatio);
|
|
float gexp = -params->filmNegative.greenExp;
|
|
float bexp = -(params->filmNegative.greenExp * params->filmNegative.blueRatio);
|
|
|
|
float rmult = params->filmNegative.refOutput.r / pow_F(rtengine::max(params->filmNegative.refInput.r, 1.f), rexp);
|
|
float gmult = params->filmNegative.refOutput.g / pow_F(rtengine::max(params->filmNegative.refInput.g, 1.f), gexp);
|
|
float bmult = params->filmNegative.refOutput.b / pow_F(rtengine::max(params->filmNegative.refInput.b, 1.f), bexp);
|
|
|
|
refInput = avg;
|
|
|
|
refOutput.r = rmult * pow_F(avg.r, rexp);
|
|
refOutput.g = gmult * pow_F(avg.g, gexp);
|
|
refOutput.b = bmult * pow_F(avg.b, bexp);
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ---------- >>> legacy mode >>> ---------------
|
|
|
|
|
|
// For backwards compatibility with profiles saved by RT 5.7 - 5.8
|
|
void rtengine::Thumbnail::processFilmNegative(
|
|
const procparams::ProcParams ¶ms,
|
|
const Imagefloat* baseImg,
|
|
const int rwidth, const int rheight
|
|
)
|
|
{
|
|
|
|
// Channel exponents
|
|
const float rexp = -params.filmNegative.redRatio * params.filmNegative.greenExp;
|
|
const float gexp = -params.filmNegative.greenExp;
|
|
const float bexp = -params.filmNegative.blueRatio * params.filmNegative.greenExp;
|
|
|
|
const float MAX_OUT_VALUE = 65000.f;
|
|
|
|
// Channel medians
|
|
float rmed, gmed, bmed;
|
|
|
|
// If using the old method, calculate medians on the whole image
|
|
calcMedians(baseImg, 0, 0, rwidth, rheight, rmed, gmed, bmed);
|
|
|
|
if (settings->verbose) {
|
|
printf("FilmNeg legacy V1 :: Thumbnail input channel medians: %g %g %g\n", rmed, gmed, bmed);
|
|
}
|
|
|
|
// Calculate output medians
|
|
rmed = powf(rmed, rexp);
|
|
gmed = powf(gmed, gexp);
|
|
bmed = powf(bmed, bexp);
|
|
|
|
// Calculate output multipliers so that the median value is 1/24 of the output range.
|
|
float rmult, gmult, bmult;
|
|
rmult = (MAX_OUT_VALUE / 24.f) / rmed;
|
|
gmult = (MAX_OUT_VALUE / 24.f) / gmed;
|
|
bmult = (MAX_OUT_VALUE / 24.f) / bmed;
|
|
|
|
float rsum = 0.f, gsum = 0.f, bsum = 0.f;
|
|
|
|
for (int i = 0; i < rheight; i++) {
|
|
for (int j = 0; j < rwidth; j++) {
|
|
rsum += baseImg->r(i, j);
|
|
gsum += baseImg->g(i, j);
|
|
bsum += baseImg->b(i, j);
|
|
}
|
|
}
|
|
|
|
const float ravg = rsum / (rheight * rwidth);
|
|
const float gavg = gsum / (rheight * rwidth);
|
|
const float bavg = bsum / (rheight * rwidth);
|
|
|
|
// Shifting current WB multipliers, based on channel averages.
|
|
rmult /= gavg / ravg;
|
|
// gmult /= gAvg / gAvg; green chosen as reference channel
|
|
bmult /= gavg / bavg;
|
|
|
|
if (settings->verbose) {
|
|
printf("FilmNeg legacy V1 :: Thumbnail computed multipliers: %g %g %g\n", static_cast<double>(rmult), static_cast<double>(gmult), static_cast<double>(bmult));
|
|
}
|
|
|
|
#ifdef __SSE2__
|
|
const vfloat clipv = F2V(MAXVALF);
|
|
const vfloat rexpv = F2V(rexp);
|
|
const vfloat gexpv = F2V(gexp);
|
|
const vfloat bexpv = F2V(bexp);
|
|
const vfloat rmultv = F2V(rmult);
|
|
const vfloat gmultv = F2V(gmult);
|
|
const vfloat bmultv = F2V(bmult);
|
|
#endif
|
|
|
|
for (int i = 0; i < rheight; i++) {
|
|
float *rline = baseImg->r(i);
|
|
float *gline = baseImg->g(i);
|
|
float *bline = baseImg->b(i);
|
|
int j = 0;
|
|
#ifdef __SSE2__
|
|
|
|
for (; j < rwidth - 3; j += 4) {
|
|
STVFU(rline[j], vminf(rmultv * pow_F(LVFU(rline[j]), rexpv), clipv));
|
|
STVFU(gline[j], vminf(gmultv * pow_F(LVFU(gline[j]), gexpv), clipv));
|
|
STVFU(bline[j], vminf(bmultv * pow_F(LVFU(bline[j]), bexpv), clipv));
|
|
}
|
|
|
|
#endif
|
|
|
|
for (; j < rwidth; ++j) {
|
|
rline[j] = CLIP(rmult * pow_F(rline[j], rexp));
|
|
gline[j] = CLIP(gmult * pow_F(gline[j], gexp));
|
|
bline[j] = CLIP(bmult * pow_F(bline[j], bexp));
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// For backwards compatibility with intermediate dev version (see filmneg_stable_mults branch)
|
|
void rtengine::Thumbnail::processFilmNegativeV2(
|
|
const procparams::ProcParams ¶ms,
|
|
const Imagefloat* baseImg,
|
|
const int rwidth, const int rheight
|
|
)
|
|
{
|
|
|
|
// Channel exponents
|
|
const float rexp = -params.filmNegative.redRatio * params.filmNegative.greenExp;
|
|
const float gexp = -params.filmNegative.greenExp;
|
|
const float bexp = -params.filmNegative.blueRatio * params.filmNegative.greenExp;
|
|
|
|
// Calculate output multipliers
|
|
float rmult, gmult, bmult;
|
|
|
|
const float MAX_OUT_VALUE = 65000.f;
|
|
|
|
// If the film base values are not set in params, estimate multipliers from each channel's median value.
|
|
if (params.filmNegative.refInput.r <= 0.f) {
|
|
|
|
// Channel medians
|
|
float rmed, gmed, bmed;
|
|
|
|
// The new method cuts out a 20% border from medians calculation.
|
|
const int bW = rwidth * 20 / 100;
|
|
const int bH = rheight * 20 / 100;
|
|
calcMedians(baseImg, bW, bH, rwidth - bW, rheight - bH, rmed, gmed, bmed);
|
|
|
|
if (settings->verbose) {
|
|
printf("FilmNeg legacy V2 :: Thumbnail input channel medians: %g %g %g\n", rmed, gmed, bmed);
|
|
}
|
|
|
|
// Calculate output medians
|
|
rmed = powf(rmed, rexp);
|
|
gmed = powf(gmed, gexp);
|
|
bmed = powf(bmed, bexp);
|
|
|
|
// Calculate output multipliers so that the median value is 1/24 of the output range.
|
|
rmult = (MAX_OUT_VALUE / 24.f) / rmed;
|
|
gmult = (MAX_OUT_VALUE / 24.f) / gmed;
|
|
bmult = (MAX_OUT_VALUE / 24.f) / bmed;
|
|
|
|
} else {
|
|
|
|
// Read film-base values from params
|
|
float rbase = params.filmNegative.refInput.r; // redBase;
|
|
float gbase = params.filmNegative.refInput.g; // greenBase;
|
|
float bbase = params.filmNegative.refInput.b; // blueBase;
|
|
|
|
// Reconstruct scale_mul coefficients from thumbnail metadata:
|
|
// redMultiplier / camwbRed is pre_mul[0]
|
|
// pre_mul[0] * scaleGain is scale_mul[0]
|
|
// Apply channel scaling to raw (unscaled) input base values, to
|
|
// match with actual (scaled) data in baseImg
|
|
rbase *= (redMultiplier / camwbRed) * scaleGain;
|
|
gbase *= (greenMultiplier / camwbGreen) * scaleGain;
|
|
bbase *= (blueMultiplier / camwbBlue) * scaleGain;
|
|
|
|
if (settings->verbose) {
|
|
printf("FilmNeg legacy V2 :: Thumbnail input film base values: %g %g %g\n", rbase, gbase, bbase);
|
|
}
|
|
|
|
// Apply exponents to get output film base values
|
|
rbase = powf(rbase, rexp);
|
|
gbase = powf(gbase, gexp);
|
|
bbase = powf(bbase, bexp);
|
|
|
|
// Calculate multipliers so that film base value is 1/512th of the output range.
|
|
rmult = (MAX_OUT_VALUE / 512.f) / rbase;
|
|
gmult = (MAX_OUT_VALUE / 512.f) / gbase;
|
|
bmult = (MAX_OUT_VALUE / 512.f) / bbase;
|
|
|
|
}
|
|
|
|
|
|
// Get and un-apply multipliers to adapt the thumbnail to a known fixed WB setting,
|
|
// as in the main image processing.
|
|
|
|
double r, g, b;
|
|
ColorTemp(3500., 1., 1., "Custom").getMultipliers(r, g, b);
|
|
//iColorMatrix is cam_rgb
|
|
const double rm = camwbRed / (iColorMatrix[0][0] * r + iColorMatrix[0][1] * g + iColorMatrix[0][2] * b);
|
|
const double gm = camwbGreen / (iColorMatrix[1][0] * r + iColorMatrix[1][1] * g + iColorMatrix[1][2] * b);
|
|
const double bm = camwbBlue / (iColorMatrix[2][0] * r + iColorMatrix[2][1] * g + iColorMatrix[2][2] * b);
|
|
|
|
// Normalize max WB multiplier to 1.f
|
|
const double m = max(rm, gm, bm);
|
|
rmult /= rm / m;
|
|
gmult /= gm / m;
|
|
bmult /= bm / m;
|
|
|
|
|
|
if (settings->verbose) {
|
|
printf("FilmNeg legacy V2 :: Thumbnail computed multipliers: %g %g %g\n", static_cast<double>(rmult), static_cast<double>(gmult), static_cast<double>(bmult));
|
|
}
|
|
|
|
|
|
#ifdef __SSE2__
|
|
const vfloat clipv = F2V(MAXVALF);
|
|
const vfloat rexpv = F2V(rexp);
|
|
const vfloat gexpv = F2V(gexp);
|
|
const vfloat bexpv = F2V(bexp);
|
|
const vfloat rmultv = F2V(rmult);
|
|
const vfloat gmultv = F2V(gmult);
|
|
const vfloat bmultv = F2V(bmult);
|
|
#endif
|
|
|
|
for (int i = 0; i < rheight; i++) {
|
|
float *rline = baseImg->r(i);
|
|
float *gline = baseImg->g(i);
|
|
float *bline = baseImg->b(i);
|
|
int j = 0;
|
|
#ifdef __SSE2__
|
|
|
|
for (; j < rwidth - 3; j += 4) {
|
|
STVFU(rline[j], vminf(rmultv * pow_F(LVFU(rline[j]), rexpv), clipv));
|
|
STVFU(gline[j], vminf(gmultv * pow_F(LVFU(gline[j]), gexpv), clipv));
|
|
STVFU(bline[j], vminf(bmultv * pow_F(LVFU(bline[j]), bexpv), clipv));
|
|
}
|
|
|
|
#endif
|
|
|
|
for (; j < rwidth; ++j) {
|
|
rline[j] = CLIP(rmult * pow_F(rline[j], rexp));
|
|
gline[j] = CLIP(gmult * pow_F(gline[j], gexp));
|
|
bline[j] = CLIP(bmult * pow_F(bline[j], bexp));
|
|
}
|
|
}
|
|
}
|
|
|
|
// ----------------- <<< legacy mode <<< ------------
|
|
|
|
|