/* SPDX-License-Identifier: GPL-2.0-or-later * Copyright 2014 Blender Foundation. */ #include "MEM_guardedalloc.h" #include "COM_SunBeamsOperation.h" namespace blender::compositor { SunBeamsOperation::SunBeamsOperation() { this->add_input_socket(DataType::Color); this->add_output_socket(DataType::Color); this->set_canvas_input_index(0); flags_.complex = true; } void SunBeamsOperation::calc_rays_common_data() { /* convert to pixels */ source_px_[0] = data_.source[0] * this->get_width(); source_px_[1] = data_.source[1] * this->get_height(); ray_length_px_ = data_.ray_length * MAX2(this->get_width(), this->get_height()); } void SunBeamsOperation::init_execution() { calc_rays_common_data(); } /** * Defines a line accumulator for a specific sector, * given by the four matrix entries that rotate from buffer space into the sector * * (x,y) is used to designate buffer space coordinates * (u,v) is used to designate sector space coordinates * * For a target point (x,y) the sector should be chosen such that * `u >= v >= 0` * This removes the need to handle all sorts of special cases. * * Template parameters: * \param fxu: buffer increment in x for sector `u + 1`. * \param fxv: buffer increment in x for sector `v + 1`. * \param fyu: buffer increment in y for sector `u + 1`. * \param fyv: buffer increment in y for sector `v + 1`. */ template struct BufferLineAccumulator { /* utility functions implementing the matrix transform to/from sector space */ static inline void buffer_to_sector(const float source[2], float x, float y, float &u, float &v) { int x0 = int(source[0]); int y0 = int(source[1]); x -= float(x0); y -= float(y0); u = x * fxu + y * fyu; v = x * fxv + y * fyv; } static inline void sector_to_buffer(const float source[2], int u, int v, int &x, int &y) { int x0 = int(source[0]); int y0 = int(source[1]); x = x0 + u * fxu + v * fxv; y = y0 + u * fyu + v * fyv; } /** * Set up the initial buffer pointer and calculate necessary variables for looping. * * Note that sector space is centered around the "source" point while the loop starts * at dist_min from the target pt. This way the loop can be canceled as soon as it runs * out of the buffer rect, because no pixels further along the line can contribute. * * \param x, y: Start location in the buffer * \param num: Total steps in the loop * \param v, dv: Vertical offset in sector space, for line offset perpendicular to the loop axis */ static float *init_buffer_iterator(MemoryBuffer *input, const float source[2], const float co[2], float dist_min, float dist_max, int &x, int &y, int &num, float &v, float &dv, float &falloff_factor) { float pu, pv; buffer_to_sector(source, co[0], co[1], pu, pv); /* line angle */ double tan_phi = pv / double(pu); double dr = sqrt(tan_phi * tan_phi + 1.0); double cos_phi = 1.0 / dr; /* clamp u range to avoid influence of pixels "behind" the source */ float umin = max_ff(pu - cos_phi * dist_min, 0.0f); float umax = max_ff(pu - cos_phi * dist_max, 0.0f); v = umin * tan_phi; dv = tan_phi; int start = int(floorf(umax)); int end = int(ceilf(umin)); num = end - start; sector_to_buffer(source, end, int(ceilf(v)), x, y); falloff_factor = dist_max > dist_min ? dr / double(dist_max - dist_min) : 0.0f; float *iter = input->get_buffer() + input->get_coords_offset(x, y); return iter; } /** * Perform the actual accumulation along a ray segment from source to pt. * Only pixels within dist_min..dist_max contribute. * * The loop runs backwards(!) over the primary sector space axis u, i.e. increasing distance to * pt. After each step it decrements v by dv < 1, adding a buffer shift when necessary. */ static void eval(MemoryBuffer *input, float output[4], const float co[2], const float source[2], float dist_min, float dist_max) { const rcti &rect = input->get_rect(); int x, y, num; float v, dv; float falloff_factor; float border[4]; zero_v4(output); if (int(co[0] - source[0]) == 0 && int(co[1] - source[1]) == 0) { copy_v4_v4(output, input->get_elem(source[0], source[1])); return; } /* Initialize the iteration variables. */ float *buffer = init_buffer_iterator( input, source, co, dist_min, dist_max, x, y, num, v, dv, falloff_factor); zero_v3(border); border[3] = 1.0f; /* v_local keeps track of when to decrement v (see below) */ float v_local = v - floorf(v); for (int i = 0; i < num; i++) { float weight = 1.0f - float(i) * falloff_factor; weight *= weight; /* range check, use last valid color when running beyond the image border */ if (x >= rect.xmin && x < rect.xmax && y >= rect.ymin && y < rect.ymax) { madd_v4_v4fl(output, buffer, buffer[3] * weight); /* use as border color in case subsequent pixels are out of bounds */ copy_v4_v4(border, buffer); } else { madd_v4_v4fl(output, border, border[3] * weight); } /* TODO: implement proper filtering here, see * https://en.wikipedia.org/wiki/Lanczos_resampling * https://en.wikipedia.org/wiki/Sinc_function * * using lanczos with x = distance from the line segment, * normalized to a == 0.5f, could give a good result * * for now just divide equally at the end ... */ /* decrement u */ x -= fxu; y -= fyu; buffer -= fxu * input->elem_stride + fyu * input->row_stride; /* decrement v (in steps of dv < 1) */ v_local -= dv; if (v_local < 0.0f) { v_local += 1.0f; x -= fxv; y -= fyv; buffer -= fxv * input->elem_stride + fyv * input->row_stride; } } /* normalize */ if (num > 0) { mul_v4_fl(output, 1.0f / float(num)); } } }; /** * Dispatch function which selects an appropriate accumulator based on the sector of the target * point, relative to the source. * * The BufferLineAccumulator defines the actual loop over the buffer, with an efficient inner loop * due to using compile time constants instead of a local matrix variable defining the sector * space. */ static void accumulate_line(MemoryBuffer *input, float output[4], const float co[2], const float source[2], float dist_min, float dist_max) { /* coordinates relative to source */ float pt_ofs[2] = {co[0] - source[0], co[1] - source[1]}; /* The source sectors are defined like so: * * \ 3 | 2 / * \ | / * 4 \ | / 1 * \|/ * ----------- * /|\ * 5 / | \ 8 * / | \ * / 6 | 7 \ * * The template arguments encode the transformation into "sector space", * by means of rotation/mirroring matrix elements. */ if (fabsf(pt_ofs[1]) > fabsf(pt_ofs[0])) { if (pt_ofs[0] > 0.0f) { if (pt_ofs[1] > 0.0f) { /* 2 */ BufferLineAccumulator<0, 1, 1, 0>::eval(input, output, co, source, dist_min, dist_max); } else { /* 7 */ BufferLineAccumulator<0, 1, -1, 0>::eval(input, output, co, source, dist_min, dist_max); } } else { if (pt_ofs[1] > 0.0f) { /* 3 */ BufferLineAccumulator<0, -1, 1, 0>::eval(input, output, co, source, dist_min, dist_max); } else { /* 6 */ BufferLineAccumulator<0, -1, -1, 0>::eval(input, output, co, source, dist_min, dist_max); } } } else { if (pt_ofs[0] > 0.0f) { if (pt_ofs[1] > 0.0f) { /* 1 */ BufferLineAccumulator<1, 0, 0, 1>::eval(input, output, co, source, dist_min, dist_max); } else { /* 8 */ BufferLineAccumulator<1, 0, 0, -1>::eval(input, output, co, source, dist_min, dist_max); } } else { if (pt_ofs[1] > 0.0f) { /* 4 */ BufferLineAccumulator<-1, 0, 0, 1>::eval(input, output, co, source, dist_min, dist_max); } else { /* 5 */ BufferLineAccumulator<-1, 0, 0, -1>::eval(input, output, co, source, dist_min, dist_max); } } } } void *SunBeamsOperation::initialize_tile_data(rcti * /*rect*/) { void *buffer = get_input_operation(0)->initialize_tile_data(nullptr); return buffer; } void SunBeamsOperation::execute_pixel(float output[4], int x, int y, void *data) { const float co[2] = {float(x), float(y)}; accumulate_line((MemoryBuffer *)data, output, co, source_px_, 0.0f, ray_length_px_); } static void calc_ray_shift(rcti *rect, float x, float y, const float source[2], float ray_length) { float co[2] = {float(x), float(y)}; float dir[2], dist; /* move (x,y) vector toward the source by ray_length distance */ sub_v2_v2v2(dir, co, source); dist = normalize_v2(dir); mul_v2_fl(dir, min_ff(dist, ray_length)); sub_v2_v2(co, dir); int ico[2] = {int(co[0]), int(co[1])}; BLI_rcti_do_minmax_v(rect, ico); } bool SunBeamsOperation::determine_depending_area_of_interest(rcti *input, ReadBufferOperation *read_operation, rcti *output) { /* Enlarges the rect by moving each corner toward the source. * This is the maximum distance that pixels can influence each other * and gives a rect that contains all possible accumulated pixels. */ rcti rect = *input; calc_ray_shift(&rect, input->xmin, input->ymin, source_px_, ray_length_px_); calc_ray_shift(&rect, input->xmin, input->ymax, source_px_, ray_length_px_); calc_ray_shift(&rect, input->xmax, input->ymin, source_px_, ray_length_px_); calc_ray_shift(&rect, input->xmax, input->ymax, source_px_, ray_length_px_); return NodeOperation::determine_depending_area_of_interest(&rect, read_operation, output); } void SunBeamsOperation::get_area_of_interest(const int input_idx, const rcti &output_area, rcti &r_input_area) { BLI_assert(input_idx == 0); UNUSED_VARS(input_idx); calc_rays_common_data(); r_input_area = output_area; /* Enlarges the rect by moving each corner toward the source. * This is the maximum distance that pixels can influence each other * and gives a rect that contains all possible accumulated pixels. */ calc_ray_shift(&r_input_area, output_area.xmin, output_area.ymin, source_px_, ray_length_px_); calc_ray_shift(&r_input_area, output_area.xmin, output_area.ymax, source_px_, ray_length_px_); calc_ray_shift(&r_input_area, output_area.xmax, output_area.ymin, source_px_, ray_length_px_); calc_ray_shift(&r_input_area, output_area.xmax, output_area.ymax, source_px_, ray_length_px_); } void SunBeamsOperation::update_memory_buffer_partial(MemoryBuffer *output, const rcti &area, Span inputs) { MemoryBuffer *input = inputs[0]; float coords[2]; for (int y = area.ymin; y < area.ymax; y++) { coords[1] = y; float *out_elem = output->get_elem(area.xmin, y); for (int x = area.xmin; x < area.xmax; x++) { coords[0] = x; accumulate_line(input, out_elem, coords, source_px_, 0.0f, ray_length_px_); out_elem += output->elem_stride; } } } } // namespace blender::compositor