From f3591ae239781d0587a141e14633e172ba9e376b Mon Sep 17 00:00:00 2001 From: "Steinar H. Gunderson" Date: Sun, 9 Mar 2014 21:55:39 +0100 Subject: [PATCH] Add an effect to do re-slicing of the image, for overlap/discard. This is another step on the way to making FFT convolutions useful. --- Makefile.in | 1 + slice_effect.cpp | 76 +++++++++++++++++++++++++++ slice_effect.frag | 26 ++++++++++ slice_effect.h | 43 ++++++++++++++++ slice_effect_test.cpp | 117 ++++++++++++++++++++++++++++++++++++++++++ util.cpp | 5 ++ util.h | 3 ++ 7 files changed, 271 insertions(+) create mode 100644 slice_effect.cpp create mode 100644 slice_effect.frag create mode 100644 slice_effect.h create mode 100644 slice_effect_test.cpp diff --git a/Makefile.in b/Makefile.in index 759cf58..db9628d 100644 --- a/Makefile.in +++ b/Makefile.in @@ -55,6 +55,7 @@ TESTED_EFFECTS += dither_effect TESTED_EFFECTS += deconvolution_sharpen_effect TESTED_EFFECTS += fft_pass_effect TESTED_EFFECTS += vignette_effect +TESTED_EFFECTS += slice_effect UNTESTED_EFFECTS = sandbox_effect UNTESTED_EFFECTS += mirror_effect diff --git a/slice_effect.cpp b/slice_effect.cpp new file mode 100644 index 0000000..09b91b9 --- /dev/null +++ b/slice_effect.cpp @@ -0,0 +1,76 @@ +#include + +#include "slice_effect.h" +#include "effect_util.h" +#include "util.h" + +using namespace std; + +namespace movit { + +SliceEffect::SliceEffect() +{ + register_int("input_slice_size", &input_slice_size); + register_int("output_slice_size", &output_slice_size); + register_int("direction", (int *)&direction); +} + +string SliceEffect::output_fragment_shader() +{ + char buf[256]; + sprintf(buf, "#define DIRECTION_VERTICAL %d\n", (direction == VERTICAL)); + return buf + read_file("slice_effect.frag"); +} + +void SliceEffect::inform_input_size(unsigned input_num, unsigned width, unsigned height) +{ + assert(input_num == 0); + input_width = width; + input_height = height; +} + +void SliceEffect::get_output_size(unsigned *width, unsigned *height, + unsigned *virtual_width, unsigned *virtual_height) const +{ + if (direction == HORIZONTAL) { + *width = div_round_up(input_width, input_slice_size) * output_slice_size; + *height = input_height; + } else { + *width = input_width; + *height = div_round_up(input_height, input_slice_size) * output_slice_size; + } + *virtual_width = *width; + *virtual_height = *height; +} + +void SliceEffect::set_gl_state(GLuint glsl_program_num, const string &prefix, unsigned *sampler_num) +{ + Effect::set_gl_state(glsl_program_num, prefix, sampler_num); + + unsigned output_width, output_height; + get_output_size(&output_width, &output_height, &output_width, &output_height); + + if (direction == HORIZONTAL) { + set_uniform_float(glsl_program_num, prefix, "output_coord_to_slice_num", float(output_width) / float(output_slice_size)); + set_uniform_float(glsl_program_num, prefix, "slice_num_to_input_coord", float(input_slice_size) / float(input_width)); + set_uniform_float(glsl_program_num, prefix, "slice_offset_to_input_coord", float(output_slice_size) / float(input_width)); + } else { + set_uniform_float(glsl_program_num, prefix, "output_coord_to_slice_num", float(output_height) / float(output_slice_size)); + set_uniform_float(glsl_program_num, prefix, "slice_num_to_input_coord", float(input_slice_size) / float(input_height)); + set_uniform_float(glsl_program_num, prefix, "slice_offset_to_input_coord", float(output_slice_size) / float(input_height)); + } + + // Normalized coordinates could potentially cause blurring of the + // image; it's not critical, but we have set changes_output_size() + // and needs_texture_bounce(), so simply turning off the interpolation + // is allowed. + assert(*sampler_num == 1); + glActiveTexture(GL_TEXTURE0); + check_error(); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + check_error(); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + check_error(); +} + +} // namespace movit diff --git a/slice_effect.frag b/slice_effect.frag new file mode 100644 index 0000000..8416515 --- /dev/null +++ b/slice_effect.frag @@ -0,0 +1,26 @@ +uniform float PREFIX(output_coord_to_slice_num); +uniform float PREFIX(slice_num_to_input_coord); +uniform float PREFIX(slice_offset_to_input_coord); + +vec4 FUNCNAME(vec2 tc) { + // DIRECTION_VERTICAL will be #defined to 1 if we are expanding vertically, + // and 0 otherwise. +#if DIRECTION_VERTICAL + float sliced_coord = tc.y; +#else + float sliced_coord = tc.x; +#endif + + // Find out which slice we are in, and a 0..1 coordinate for the offset within that slice. + float slice_num = floor(sliced_coord * PREFIX(output_coord_to_slice_num)); + float slice_offset = fract(sliced_coord * PREFIX(output_coord_to_slice_num)); + + // Find out where this slice begins in the input data, and then offset from that. + float input_coord = slice_num * PREFIX(slice_num_to_input_coord) + slice_offset * PREFIX(slice_offset_to_input_coord); + +#if DIRECTION_VERTICAL + return INPUT(vec2(tc.x, input_coord)); +#else + return INPUT(vec2(input_coord, tc.y)); +#endif +} diff --git a/slice_effect.h b/slice_effect.h new file mode 100644 index 0000000..6380c16 --- /dev/null +++ b/slice_effect.h @@ -0,0 +1,43 @@ +#ifndef _MOVIT_SLICE_EFFECT_H +#define _MOVIT_SLICE_EFFECT_H 1 + +// SliceEffect takes an image, cuts it into (potentially overlapping) slices, +// and puts those slices back together again consecutively. It is primarily +// useful in an overlap-discard setting, where it can do both the overlap and +// discard roles, where one does convolutions by means of many small FFTs, but +// could also work as a (relatively boring) video effect on its own. +// +// Note that vertical slices happen from the bottom, not the top, due to the +// OpenGL coordinate system. + +#include +#include + +#include "effect.h" + +namespace movit { + +class SliceEffect : public Effect { +public: + SliceEffect(); + virtual std::string effect_type_id() const { return "SliceEffect"; } + std::string output_fragment_shader(); + virtual bool needs_texture_bounce() const { return true; } + virtual bool changes_output_size() const { return true; } + virtual void inform_input_size(unsigned input_num, unsigned width, unsigned height); + virtual void get_output_size(unsigned *width, unsigned *height, + unsigned *virtual_width, unsigned *virtual_height) const; + + void set_gl_state(GLuint glsl_program_num, const std::string &prefix, unsigned *sampler_num); + + enum Direction { HORIZONTAL = 0, VERTICAL = 1 }; + +private: + int input_width, input_height; + int input_slice_size, output_slice_size; + Direction direction; +}; + +} // namespace movit + +#endif // !defined(_MOVIT_SLICE_EFFECT_H) diff --git a/slice_effect_test.cpp b/slice_effect_test.cpp new file mode 100644 index 0000000..efa6803 --- /dev/null +++ b/slice_effect_test.cpp @@ -0,0 +1,117 @@ +// Unit tests for SliceEffect. + +#include + +#include "effect_chain.h" +#include "flat_input.h" +#include "gtest/gtest.h" +#include "image_format.h" +#include "input.h" +#include "slice_effect.h" +#include "test_util.h" + +namespace movit { + +TEST(SliceEffectTest, Identity) { + const int size = 3, output_size = 4; + float data[size * size] = { + 0.0f, 0.1f, 0.2f, + 0.4f, 0.3f, 0.8f, + 0.5f, 0.2f, 0.1f, + }; + float expected_data[output_size * size] = { + 0.0f, 0.1f, 0.2f, 0.2f, + 0.4f, 0.3f, 0.8f, 0.8f, + 0.5f, 0.2f, 0.1f, 0.1f, + }; + float out_data[output_size * size]; + + EffectChainTester tester(NULL, output_size, size, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR); + tester.add_input(data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR, size, size); + + Effect *slice_effect = tester.get_chain()->add_effect(new SliceEffect()); + ASSERT_TRUE(slice_effect->set_int("input_slice_size", 2)); + ASSERT_TRUE(slice_effect->set_int("output_slice_size", 2)); + ASSERT_TRUE(slice_effect->set_int("direction", SliceEffect::HORIZONTAL)); + tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR); + + expect_equal(expected_data, out_data, output_size, size); +} + +TEST(SliceEffectTest, HorizontalOverlap) { + float data[5 * 2] = { + 0.0f, 0.1f, 0.2f, 0.3f, 0.4f, + 0.4f, 0.3f, 0.2f, 0.1f, 0.0f, + }; + float expected_data[9 * 2] = { + 0.0f, 0.1f, 0.2f, 0.2f, 0.3f, 0.4f, 0.4f, 0.4f, 0.4f, + 0.4f, 0.3f, 0.2f, 0.2f, 0.1f, 0.0f, 0.0f, 0.0f, 0.0f, + }; + float out_data[9 * 2]; + + EffectChainTester tester(NULL, 9, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR); + tester.add_input(data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR, 5, 2); + + Effect *slice_effect = tester.get_chain()->add_effect(new SliceEffect()); + ASSERT_TRUE(slice_effect->set_int("input_slice_size", 2)); + ASSERT_TRUE(slice_effect->set_int("output_slice_size", 3)); + ASSERT_TRUE(slice_effect->set_int("direction", SliceEffect::HORIZONTAL)); + tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR); + + expect_equal(expected_data, out_data, 9, 2); +} + +TEST(SliceEffectTest, HorizontalDiscard) { + float data[6 * 2] = { + 0.0f, 0.1f, 0.2f, 0.2f, 0.3f, 0.4f, + 0.4f, 0.3f, 0.2f, 0.2f, 0.1f, 0.0f, + }; + float expected_data[4 * 2] = { + 0.0f, 0.1f, 0.2f, 0.3f, + 0.4f, 0.3f, 0.2f, 0.1f, + }; + float out_data[4 * 2]; + + EffectChainTester tester(NULL, 4, 2, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR); + tester.add_input(data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR, 6, 2); + + Effect *slice_effect = tester.get_chain()->add_effect(new SliceEffect()); + ASSERT_TRUE(slice_effect->set_int("input_slice_size", 3)); + ASSERT_TRUE(slice_effect->set_int("output_slice_size", 2)); + ASSERT_TRUE(slice_effect->set_int("direction", SliceEffect::HORIZONTAL)); + tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR); + + expect_equal(expected_data, out_data, 4, 2); +} + +TEST(SliceEffectTest, VerticalOverlapSlicesFromBottom) { + float data[2 * 3] = { + 0.0f, 0.1f, + + 0.4f, 0.3f, + 0.6f, 0.2f, + }; + float expected_data[2 * 6] = { + 0.0f, 0.1f, + 0.0f, 0.1f, + 0.0f, 0.1f, + + 0.0f, 0.1f, + 0.4f, 0.3f, + 0.6f, 0.2f, + }; + float out_data[2 * 6]; + + EffectChainTester tester(NULL, 2, 6, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR); + tester.add_input(data, FORMAT_GRAYSCALE, COLORSPACE_sRGB, GAMMA_LINEAR, 2, 3); + + Effect *slice_effect = tester.get_chain()->add_effect(new SliceEffect()); + ASSERT_TRUE(slice_effect->set_int("input_slice_size", 2)); + ASSERT_TRUE(slice_effect->set_int("output_slice_size", 3)); + ASSERT_TRUE(slice_effect->set_int("direction", SliceEffect::VERTICAL)); + tester.run(out_data, GL_RED, COLORSPACE_sRGB, GAMMA_LINEAR); + + expect_equal(expected_data, out_data, 2, 6); +} + +} // namespace movit diff --git a/util.cpp b/util.cpp index b86e3f7..8162f71 100644 --- a/util.cpp +++ b/util.cpp @@ -215,4 +215,9 @@ void cleanup_vertex_attribute(GLuint glsl_program_num, const string &attribute_n check_error(); } +unsigned div_round_up(unsigned a, unsigned b) +{ + return (a + b - 1) / b; +} + } // namespace movit diff --git a/util.h b/util.h index 627c629..c59231d 100644 --- a/util.h +++ b/util.h @@ -34,6 +34,9 @@ void print_3x3_matrix(const Eigen::Matrix3d &m); // Output a GLSL 3x3 matrix declaration. std::string output_glsl_mat3(const std::string &name, const Eigen::Matrix3d &m); +// Calculate a / b, rounding up. Does not handle overflow correctly. +unsigned div_round_up(unsigned a, unsigned b); + // Calculate where to sample, and with what weight, if one wants to use // the GPU's bilinear hardware to sample w1 * x[0] + w2 * x[1]. // -- 2.39.2