This is another step on the way to making FFT convolutions useful.
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
--- /dev/null
+#include <GL/glew.h>
+
+#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
--- /dev/null
+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
+}
--- /dev/null
+#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 <GL/glew.h>
+#include <string>
+
+#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)
--- /dev/null
+// Unit tests for SliceEffect.
+
+#include <GL/glew.h>
+
+#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
check_error();
}
+unsigned div_round_up(unsigned a, unsigned b)
+{
+ return (a + b - 1) / b;
+}
+
} // namespace movit
// 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].
//