538 lines
17 KiB
C++
538 lines
17 KiB
C++
/*
|
|
* This program 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 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This program 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 this program; if not, write to the Free Software Foundation,
|
|
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*
|
|
* Copyright 2011, Blender Foundation.
|
|
*/
|
|
|
|
#include "COM_ScreenLensDistortionOperation.h"
|
|
|
|
#include "COM_ConstantOperation.h"
|
|
|
|
#include "BLI_rand.h"
|
|
|
|
#include "PIL_time.h"
|
|
|
|
namespace blender::compositor {
|
|
|
|
ScreenLensDistortionOperation::ScreenLensDistortionOperation()
|
|
{
|
|
this->add_input_socket(DataType::Color);
|
|
this->add_input_socket(DataType::Value);
|
|
this->add_input_socket(DataType::Value);
|
|
this->add_output_socket(DataType::Color);
|
|
flags_.complex = true;
|
|
input_program_ = nullptr;
|
|
distortion_ = 0.0f;
|
|
dispersion_ = 0.0f;
|
|
distortion_const_ = false;
|
|
dispersion_const_ = false;
|
|
variables_ready_ = false;
|
|
}
|
|
|
|
void ScreenLensDistortionOperation::set_distortion(float distortion)
|
|
{
|
|
distortion_ = distortion;
|
|
distortion_const_ = true;
|
|
}
|
|
|
|
void ScreenLensDistortionOperation::set_dispersion(float dispersion)
|
|
{
|
|
dispersion_ = dispersion;
|
|
dispersion_const_ = true;
|
|
}
|
|
|
|
void ScreenLensDistortionOperation::init_data()
|
|
{
|
|
cx_ = 0.5f * (float)get_width();
|
|
cy_ = 0.5f * (float)get_height();
|
|
|
|
switch (execution_model_) {
|
|
case eExecutionModel::FullFrame: {
|
|
NodeOperation *distortion_op = get_input_operation(1);
|
|
NodeOperation *dispersion_op = get_input_operation(2);
|
|
if (!distortion_const_ && distortion_op->get_flags().is_constant_operation) {
|
|
distortion_ = static_cast<ConstantOperation *>(distortion_op)->get_constant_elem()[0];
|
|
}
|
|
if (!dispersion_const_ && distortion_op->get_flags().is_constant_operation) {
|
|
dispersion_ = static_cast<ConstantOperation *>(dispersion_op)->get_constant_elem()[0];
|
|
}
|
|
update_variables(distortion_, dispersion_);
|
|
break;
|
|
}
|
|
case eExecutionModel::Tiled: {
|
|
/* If both are constant, init variables once. */
|
|
if (distortion_const_ && dispersion_const_) {
|
|
update_variables(distortion_, dispersion_);
|
|
variables_ready_ = true;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void ScreenLensDistortionOperation::init_execution()
|
|
{
|
|
input_program_ = this->get_input_socket_reader(0);
|
|
this->init_mutex();
|
|
|
|
uint rng_seed = (uint)(PIL_check_seconds_timer_i() & UINT_MAX);
|
|
rng_seed ^= (uint)POINTER_AS_INT(input_program_);
|
|
rng_ = BLI_rng_new(rng_seed);
|
|
}
|
|
|
|
void *ScreenLensDistortionOperation::initialize_tile_data(rcti * /*rect*/)
|
|
{
|
|
void *buffer = input_program_->initialize_tile_data(nullptr);
|
|
|
|
/* get distortion/dispersion values once, by reading inputs at (0,0)
|
|
* XXX this assumes invariable values (no image inputs),
|
|
* we don't have a nice generic system for that yet
|
|
*/
|
|
if (!variables_ready_) {
|
|
this->lock_mutex();
|
|
|
|
if (!distortion_const_) {
|
|
float result[4];
|
|
get_input_socket_reader(1)->read_sampled(result, 0, 0, PixelSampler::Nearest);
|
|
distortion_ = result[0];
|
|
}
|
|
if (!dispersion_const_) {
|
|
float result[4];
|
|
get_input_socket_reader(2)->read_sampled(result, 0, 0, PixelSampler::Nearest);
|
|
dispersion_ = result[0];
|
|
}
|
|
|
|
update_variables(distortion_, dispersion_);
|
|
variables_ready_ = true;
|
|
|
|
this->unlock_mutex();
|
|
}
|
|
|
|
return buffer;
|
|
}
|
|
|
|
void ScreenLensDistortionOperation::get_uv(const float xy[2], float uv[2]) const
|
|
{
|
|
uv[0] = sc_ * ((xy[0] + 0.5f) - cx_) / cx_;
|
|
uv[1] = sc_ * ((xy[1] + 0.5f) - cy_) / cy_;
|
|
}
|
|
|
|
void ScreenLensDistortionOperation::distort_uv(const float uv[2], float t, float xy[2]) const
|
|
{
|
|
float d = 1.0f / (1.0f + sqrtf(t));
|
|
xy[0] = (uv[0] * d + 0.5f) * get_width() - 0.5f;
|
|
xy[1] = (uv[1] * d + 0.5f) * get_height() - 0.5f;
|
|
}
|
|
|
|
bool ScreenLensDistortionOperation::get_delta(float r_sq,
|
|
float k4,
|
|
const float uv[2],
|
|
float delta[2]) const
|
|
{
|
|
float t = 1.0f - k4 * r_sq;
|
|
if (t >= 0.0f) {
|
|
distort_uv(uv, t, delta);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void ScreenLensDistortionOperation::accumulate(const MemoryBuffer *buffer,
|
|
int a,
|
|
int b,
|
|
float r_sq,
|
|
const float uv[2],
|
|
const float delta[3][2],
|
|
float sum[4],
|
|
int count[3]) const
|
|
{
|
|
float color[4];
|
|
|
|
float dsf = len_v2v2(delta[a], delta[b]) + 1.0f;
|
|
int ds = jitter_ ? (dsf < 4.0f ? 2 : (int)sqrtf(dsf)) : (int)dsf;
|
|
float sd = 1.0f / (float)ds;
|
|
|
|
float k4 = k4_[a];
|
|
float dk4 = dk4_[a];
|
|
|
|
for (float z = 0; z < ds; z++) {
|
|
float tz = (z + (jitter_ ? BLI_rng_get_float(rng_) : 0.5f)) * sd;
|
|
float t = 1.0f - (k4 + tz * dk4) * r_sq;
|
|
|
|
float xy[2];
|
|
distort_uv(uv, t, xy);
|
|
switch (execution_model_) {
|
|
case eExecutionModel::Tiled:
|
|
buffer->read_bilinear(color, xy[0], xy[1]);
|
|
break;
|
|
case eExecutionModel::FullFrame:
|
|
buffer->read_elem_bilinear(xy[0], xy[1], color);
|
|
break;
|
|
}
|
|
|
|
sum[a] += (1.0f - tz) * color[a];
|
|
sum[b] += (tz)*color[b];
|
|
count[a]++;
|
|
count[b]++;
|
|
}
|
|
}
|
|
|
|
void ScreenLensDistortionOperation::execute_pixel(float output[4], int x, int y, void *data)
|
|
{
|
|
MemoryBuffer *buffer = (MemoryBuffer *)data;
|
|
float xy[2] = {(float)x, (float)y};
|
|
float uv[2];
|
|
get_uv(xy, uv);
|
|
float uv_dot = len_squared_v2(uv);
|
|
|
|
int count[3] = {0, 0, 0};
|
|
float delta[3][2];
|
|
float sum[4] = {0, 0, 0, 0};
|
|
|
|
bool valid_r = get_delta(uv_dot, k4_[0], uv, delta[0]);
|
|
bool valid_g = get_delta(uv_dot, k4_[1], uv, delta[1]);
|
|
bool valid_b = get_delta(uv_dot, k4_[2], uv, delta[2]);
|
|
|
|
if (valid_r && valid_g && valid_b) {
|
|
accumulate(buffer, 0, 1, uv_dot, uv, delta, sum, count);
|
|
accumulate(buffer, 1, 2, uv_dot, uv, delta, sum, count);
|
|
|
|
if (count[0]) {
|
|
output[0] = 2.0f * sum[0] / (float)count[0];
|
|
}
|
|
if (count[1]) {
|
|
output[1] = 2.0f * sum[1] / (float)count[1];
|
|
}
|
|
if (count[2]) {
|
|
output[2] = 2.0f * sum[2] / (float)count[2];
|
|
}
|
|
|
|
/* set alpha */
|
|
output[3] = 1.0f;
|
|
}
|
|
else {
|
|
zero_v4(output);
|
|
}
|
|
}
|
|
|
|
void ScreenLensDistortionOperation::deinit_execution()
|
|
{
|
|
this->deinit_mutex();
|
|
input_program_ = nullptr;
|
|
BLI_rng_free(rng_);
|
|
}
|
|
|
|
void ScreenLensDistortionOperation::determineUV(float result[6], float x, float y) const
|
|
{
|
|
const float xy[2] = {x, y};
|
|
float uv[2];
|
|
get_uv(xy, uv);
|
|
float uv_dot = len_squared_v2(uv);
|
|
|
|
copy_v2_v2(result + 0, xy);
|
|
copy_v2_v2(result + 2, xy);
|
|
copy_v2_v2(result + 4, xy);
|
|
get_delta(uv_dot, k4_[0], uv, result + 0);
|
|
get_delta(uv_dot, k4_[1], uv, result + 2);
|
|
get_delta(uv_dot, k4_[2], uv, result + 4);
|
|
}
|
|
|
|
bool ScreenLensDistortionOperation::determine_depending_area_of_interest(
|
|
rcti * /*input*/, ReadBufferOperation *read_operation, rcti *output)
|
|
{
|
|
rcti new_input_value;
|
|
new_input_value.xmin = 0;
|
|
new_input_value.ymin = 0;
|
|
new_input_value.xmax = 2;
|
|
new_input_value.ymax = 2;
|
|
|
|
NodeOperation *operation = get_input_operation(1);
|
|
if (operation->determine_depending_area_of_interest(&new_input_value, read_operation, output)) {
|
|
return true;
|
|
}
|
|
|
|
operation = get_input_operation(2);
|
|
if (operation->determine_depending_area_of_interest(&new_input_value, read_operation, output)) {
|
|
return true;
|
|
}
|
|
|
|
/* XXX the original method of estimating the area-of-interest does not work
|
|
* it assumes a linear increase/decrease of mapped coordinates, which does not
|
|
* yield correct results for the area and leaves uninitialized buffer areas.
|
|
* So now just use the full image area, which may not be as efficient but works at least ...
|
|
*/
|
|
#if 1
|
|
rcti image_input;
|
|
|
|
operation = get_input_operation(0);
|
|
image_input.xmax = operation->get_width();
|
|
image_input.xmin = 0;
|
|
image_input.ymax = operation->get_height();
|
|
image_input.ymin = 0;
|
|
|
|
if (operation->determine_depending_area_of_interest(&image_input, read_operation, output)) {
|
|
return true;
|
|
}
|
|
return false;
|
|
#else
|
|
rcti new_input;
|
|
const float margin = 2;
|
|
|
|
BLI_rcti_init_minmax(&new_input);
|
|
|
|
if (dispersion_const_ && distortion_const_) {
|
|
/* update from fixed distortion/dispersion */
|
|
# define UPDATE_INPUT(x, y) \
|
|
{ \
|
|
float coords[6]; \
|
|
determineUV(coords, x, y); \
|
|
new_input.xmin = min_ffff(new_input.xmin, coords[0], coords[2], coords[4]); \
|
|
new_input.ymin = min_ffff(new_input.ymin, coords[1], coords[3], coords[5]); \
|
|
new_input.xmax = max_ffff(new_input.xmax, coords[0], coords[2], coords[4]); \
|
|
new_input.ymax = max_ffff(new_input.ymax, coords[1], coords[3], coords[5]); \
|
|
} \
|
|
(void)0
|
|
|
|
UPDATE_INPUT(input->xmin, input->xmax);
|
|
UPDATE_INPUT(input->xmin, input->ymax);
|
|
UPDATE_INPUT(input->xmax, input->ymax);
|
|
UPDATE_INPUT(input->xmax, input->ymin);
|
|
|
|
# undef UPDATE_INPUT
|
|
}
|
|
else {
|
|
/* use maximum dispersion 1.0 if not const */
|
|
float dispersion = dispersion_const_ ? dispersion_ : 1.0f;
|
|
|
|
# define UPDATE_INPUT(x, y, distortion) \
|
|
{ \
|
|
float coords[6]; \
|
|
update_variables(distortion, dispersion); \
|
|
determineUV(coords, x, y); \
|
|
new_input.xmin = min_ffff(new_input.xmin, coords[0], coords[2], coords[4]); \
|
|
new_input.ymin = min_ffff(new_input.ymin, coords[1], coords[3], coords[5]); \
|
|
new_input.xmax = max_ffff(new_input.xmax, coords[0], coords[2], coords[4]); \
|
|
new_input.ymax = max_ffff(new_input.ymax, coords[1], coords[3], coords[5]); \
|
|
} \
|
|
(void)0
|
|
|
|
if (distortion_const_) {
|
|
/* update from fixed distortion */
|
|
UPDATE_INPUT(input->xmin, input->xmax, distortion_);
|
|
UPDATE_INPUT(input->xmin, input->ymax, distortion_);
|
|
UPDATE_INPUT(input->xmax, input->ymax, distortion_);
|
|
UPDATE_INPUT(input->xmax, input->ymin, distortion_);
|
|
}
|
|
else {
|
|
/* update from min/max distortion (-1..1) */
|
|
UPDATE_INPUT(input->xmin, input->xmax, -1.0f);
|
|
UPDATE_INPUT(input->xmin, input->ymax, -1.0f);
|
|
UPDATE_INPUT(input->xmax, input->ymax, -1.0f);
|
|
UPDATE_INPUT(input->xmax, input->ymin, -1.0f);
|
|
|
|
UPDATE_INPUT(input->xmin, input->xmax, 1.0f);
|
|
UPDATE_INPUT(input->xmin, input->ymax, 1.0f);
|
|
UPDATE_INPUT(input->xmax, input->ymax, 1.0f);
|
|
UPDATE_INPUT(input->xmax, input->ymin, 1.0f);
|
|
|
|
# undef UPDATE_INPUT
|
|
}
|
|
}
|
|
|
|
new_input.xmin -= margin;
|
|
new_input.ymin -= margin;
|
|
new_input.xmax += margin;
|
|
new_input.ymax += margin;
|
|
|
|
operation = get_input_operation(0);
|
|
if (operation->determine_depending_area_of_interest(&new_input, read_operation, output)) {
|
|
return true;
|
|
}
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
void ScreenLensDistortionOperation::update_variables(float distortion, float dispersion)
|
|
{
|
|
k_[1] = max_ff(min_ff(distortion, 1.0f), -0.999f);
|
|
/* Smaller dispersion range for somewhat more control. */
|
|
float d = 0.25f * max_ff(min_ff(dispersion, 1.0f), 0.0f);
|
|
k_[0] = max_ff(min_ff((k_[1] + d), 1.0f), -0.999f);
|
|
k_[2] = max_ff(min_ff((k_[1] - d), 1.0f), -0.999f);
|
|
maxk_ = max_fff(k_[0], k_[1], k_[2]);
|
|
sc_ = (fit_ && (maxk_ > 0.0f)) ? (1.0f / (1.0f + 2.0f * maxk_)) : (1.0f / (1.0f + maxk_));
|
|
dk4_[0] = 4.0f * (k_[1] - k_[0]);
|
|
dk4_[1] = 4.0f * (k_[2] - k_[1]);
|
|
dk4_[2] = 0.0f; /* unused */
|
|
|
|
mul_v3_v3fl(k4_, k_, 4.0f);
|
|
}
|
|
|
|
void ScreenLensDistortionOperation::determine_canvas(const rcti &preferred_area, rcti &r_area)
|
|
{
|
|
switch (execution_model_) {
|
|
case eExecutionModel::FullFrame: {
|
|
set_determined_canvas_modifier([=](rcti &canvas) {
|
|
/* Ensure screen space. */
|
|
BLI_rcti_translate(&canvas, -canvas.xmin, -canvas.ymin);
|
|
});
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
|
|
NodeOperation::determine_canvas(preferred_area, r_area);
|
|
}
|
|
|
|
void ScreenLensDistortionOperation::get_area_of_interest(const int input_idx,
|
|
const rcti &UNUSED(output_area),
|
|
rcti &r_input_area)
|
|
{
|
|
if (input_idx != 0) {
|
|
/* Dispersion and distortion inputs are used as constants only. */
|
|
r_input_area = COM_CONSTANT_INPUT_AREA_OF_INTEREST;
|
|
}
|
|
|
|
/* XXX the original method of estimating the area-of-interest does not work
|
|
* it assumes a linear increase/decrease of mapped coordinates, which does not
|
|
* yield correct results for the area and leaves uninitialized buffer areas.
|
|
* So now just use the full image area, which may not be as efficient but works at least ...
|
|
*/
|
|
#if 1
|
|
NodeOperation *image = get_input_operation(0);
|
|
r_input_area = image->get_canvas();
|
|
|
|
#else /* Original method in tiled implementation. */
|
|
rcti new_input;
|
|
const float margin = 2;
|
|
|
|
BLI_rcti_init_minmax(&new_input);
|
|
|
|
if (dispersion_const_ && distortion_const_) {
|
|
/* update from fixed distortion/dispersion */
|
|
# define UPDATE_INPUT(x, y) \
|
|
{ \
|
|
float coords[6]; \
|
|
determineUV(coords, x, y); \
|
|
new_input.xmin = min_ffff(new_input.xmin, coords[0], coords[2], coords[4]); \
|
|
new_input.ymin = min_ffff(new_input.ymin, coords[1], coords[3], coords[5]); \
|
|
new_input.xmax = max_ffff(new_input.xmax, coords[0], coords[2], coords[4]); \
|
|
new_input.ymax = max_ffff(new_input.ymax, coords[1], coords[3], coords[5]); \
|
|
} \
|
|
(void)0
|
|
|
|
UPDATE_INPUT(input->xmin, input->xmax);
|
|
UPDATE_INPUT(input->xmin, input->ymax);
|
|
UPDATE_INPUT(input->xmax, input->ymax);
|
|
UPDATE_INPUT(input->xmax, input->ymin);
|
|
|
|
# undef UPDATE_INPUT
|
|
}
|
|
else {
|
|
/* use maximum dispersion 1.0 if not const */
|
|
float dispersion = dispersion_const_ ? dispersion_ : 1.0f;
|
|
|
|
# define UPDATE_INPUT(x, y, distortion) \
|
|
{ \
|
|
float coords[6]; \
|
|
update_variables(distortion, dispersion); \
|
|
determineUV(coords, x, y); \
|
|
new_input.xmin = min_ffff(new_input.xmin, coords[0], coords[2], coords[4]); \
|
|
new_input.ymin = min_ffff(new_input.ymin, coords[1], coords[3], coords[5]); \
|
|
new_input.xmax = max_ffff(new_input.xmax, coords[0], coords[2], coords[4]); \
|
|
new_input.ymax = max_ffff(new_input.ymax, coords[1], coords[3], coords[5]); \
|
|
} \
|
|
(void)0
|
|
|
|
if (distortion_const_) {
|
|
/* update from fixed distortion */
|
|
UPDATE_INPUT(input->xmin, input->xmax, distortion_);
|
|
UPDATE_INPUT(input->xmin, input->ymax, distortion_);
|
|
UPDATE_INPUT(input->xmax, input->ymax, distortion_);
|
|
UPDATE_INPUT(input->xmax, input->ymin, distortion_);
|
|
}
|
|
else {
|
|
/* update from min/max distortion (-1..1) */
|
|
UPDATE_INPUT(input->xmin, input->xmax, -1.0f);
|
|
UPDATE_INPUT(input->xmin, input->ymax, -1.0f);
|
|
UPDATE_INPUT(input->xmax, input->ymax, -1.0f);
|
|
UPDATE_INPUT(input->xmax, input->ymin, -1.0f);
|
|
|
|
UPDATE_INPUT(input->xmin, input->xmax, 1.0f);
|
|
UPDATE_INPUT(input->xmin, input->ymax, 1.0f);
|
|
UPDATE_INPUT(input->xmax, input->ymax, 1.0f);
|
|
UPDATE_INPUT(input->xmax, input->ymin, 1.0f);
|
|
|
|
# undef UPDATE_INPUT
|
|
}
|
|
}
|
|
|
|
new_input.xmin -= margin;
|
|
new_input.ymin -= margin;
|
|
new_input.xmax += margin;
|
|
new_input.ymax += margin;
|
|
|
|
operation = get_input_operation(0);
|
|
if (operation->determine_depending_area_of_interest(&new_input, read_operation, output)) {
|
|
return true;
|
|
}
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
void ScreenLensDistortionOperation::update_memory_buffer_partial(MemoryBuffer *output,
|
|
const rcti &area,
|
|
Span<MemoryBuffer *> inputs)
|
|
{
|
|
const MemoryBuffer *input_image = inputs[0];
|
|
for (BuffersIterator<float> it = output->iterate_with({}, area); !it.is_end(); ++it) {
|
|
float xy[2] = {(float)it.x, (float)it.y};
|
|
float uv[2];
|
|
get_uv(xy, uv);
|
|
const float uv_dot = len_squared_v2(uv);
|
|
|
|
float delta[3][2];
|
|
const bool valid_r = get_delta(uv_dot, k4_[0], uv, delta[0]);
|
|
const bool valid_g = get_delta(uv_dot, k4_[1], uv, delta[1]);
|
|
const bool valid_b = get_delta(uv_dot, k4_[2], uv, delta[2]);
|
|
if (!(valid_r && valid_g && valid_b)) {
|
|
zero_v4(it.out);
|
|
continue;
|
|
}
|
|
|
|
int count[3] = {0, 0, 0};
|
|
float sum[4] = {0, 0, 0, 0};
|
|
accumulate(input_image, 0, 1, uv_dot, uv, delta, sum, count);
|
|
accumulate(input_image, 1, 2, uv_dot, uv, delta, sum, count);
|
|
|
|
if (count[0]) {
|
|
it.out[0] = 2.0f * sum[0] / (float)count[0];
|
|
}
|
|
if (count[1]) {
|
|
it.out[1] = 2.0f * sum[1] / (float)count[1];
|
|
}
|
|
if (count[2]) {
|
|
it.out[2] = 2.0f * sum[2] / (float)count[2];
|
|
}
|
|
|
|
/* Set alpha. */
|
|
it.out[3] = 1.0f;
|
|
}
|
|
}
|
|
|
|
} // namespace blender::compositor
|