--- /dev/null
+#define NO_SDL_GLEXT 1
+
+#define WIDTH 1280
+#define HEIGHT 720
+
+#include <epoxy/gl.h>
+
+#include <SDL2/SDL.h>
+#include <SDL2/SDL_error.h>
+#include <SDL2/SDL_events.h>
+#include <SDL2/SDL_image.h>
+#include <SDL2/SDL_keyboard.h>
+#include <SDL2/SDL_mouse.h>
+#include <SDL2/SDL_video.h>
+
+#include <assert.h>
+#include <features.h>
+#include <math.h>
+#include <png.h>
+#include <pngconf.h>
+#include <setjmp.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/time.h>
+#include <time.h>
+
+#include <movit/effect_chain.h>
+#include <movit/flat_input.h>
+#include <movit/init.h>
+#include <movit/mix_effect.h>
+#include <movit/util.h>
+
+using namespace movit;
+
+unsigned char result[WIDTH * HEIGHT * 4];
+
+unsigned char *load_image(const char *filename, unsigned *w, unsigned *h)
+{
+ SDL_Surface *img = IMG_Load(filename);
+ if (img == nullptr) {
+ fprintf(stderr, "Load of '%s' failed\n", filename);
+ exit(1);
+ }
+
+ SDL_PixelFormat rgba_fmt;
+ rgba_fmt.palette = nullptr;
+ rgba_fmt.BitsPerPixel = 32;
+ rgba_fmt.BytesPerPixel = 8;
+ rgba_fmt.Rloss = rgba_fmt.Gloss = rgba_fmt.Bloss = rgba_fmt.Aloss = 0;
+
+ // NOTE: Assumes little endian.
+ rgba_fmt.Rmask = 0x00ff0000;
+ rgba_fmt.Gmask = 0x0000ff00;
+ rgba_fmt.Bmask = 0x000000ff;
+ rgba_fmt.Amask = 0xff000000;
+
+ rgba_fmt.Rshift = 16;
+ rgba_fmt.Gshift = 8;
+ rgba_fmt.Bshift = 0;
+ rgba_fmt.Ashift = 24;
+
+ SDL_Surface *converted = SDL_ConvertSurface(img, &rgba_fmt, SDL_SWSURFACE);
+
+ *w = img->w;
+ *h = img->h;
+
+ SDL_FreeSurface(img);
+
+ unsigned char *crop_img = new unsigned char[1280 * 720 * 4];
+
+ int img_w = *w;
+ int img_h = *h;
+
+ int xoffs = (1280 - int(img_w)) / 2;
+ int yoffs = (720 - int(img_h)) / 2;
+
+ for (unsigned y = 0; y < 720; ++y) {
+ unsigned char *dptr = crop_img + y * 1280 * 4;
+ int ysrc = y - yoffs;
+ if (ysrc < 0 || ysrc >= int(img_h)) {
+ memset(dptr, 0, 1280 * 4);
+ continue;
+ }
+ unsigned char *sptr = (unsigned char *)converted->pixels + ysrc * img_w * 4;
+ if (img_w >= 1280) {
+ memcpy(dptr, sptr - xoffs * 4, 1280 * 4);
+ } else {
+ memset(dptr, 0, 1280 * 4);
+ memcpy(dptr + xoffs * 4, sptr, img_w * 4);
+ }
+ }
+
+ SDL_FreeSurface(converted);
+
+ *w = 1280;
+ *h = 720;
+
+ return crop_img;
+}
+
+void write_png(const char *filename, unsigned char *screenbuf)
+{
+ FILE *fp = fopen(filename, "wb");
+ png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
+ png_infop info_ptr = png_create_info_struct(png_ptr);
+
+ if (setjmp(png_jmpbuf(png_ptr))) {
+ fclose(fp);
+ fprintf(stderr, "Write to %s failed; exiting.\n", filename);
+ exit(1);
+ }
+
+ png_set_IHDR(png_ptr, info_ptr, WIDTH, HEIGHT, 8, PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
+
+ png_bytep *row_pointers = new png_bytep[HEIGHT];
+ for (unsigned y = 0; y < HEIGHT; ++y) {
+ row_pointers[y] = screenbuf + ((HEIGHT - y - 1) * WIDTH) * 4;
+ }
+
+ png_init_io(png_ptr, fp);
+ png_set_rows(png_ptr, info_ptr, row_pointers);
+ png_write_png(png_ptr, info_ptr, PNG_TRANSFORM_BGR, nullptr);
+ png_destroy_write_struct(&png_ptr, &info_ptr);
+ fclose(fp);
+
+ delete[] row_pointers;
+}
+
+class BouncingIdentityEffect : public Effect {
+public:
+ BouncingIdentityEffect() {}
+ std::string effect_type_id() const override { return "IdentityEffect"; }
+ std::string output_fragment_shader() override { return read_file("identity.frag"); }
+ bool needs_texture_bounce() const override { return true; }
+ AlphaHandling alpha_handling() const override { return DONT_CARE_ALPHA_TYPE; }
+};
+
+
+int main(int argc, char **argv)
+{
+ if (SDL_Init(SDL_INIT_EVERYTHING) == -1) {
+ fprintf(stderr, "SDL_Init failed: %s\n", SDL_GetError());
+ exit(1);
+ }
+ SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8);
+ SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 0);
+ SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 0);
+ SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
+
+ SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
+ SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
+ SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1);
+ // SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_DEBUG_FLAG);
+ SDL_Window *window = SDL_CreateWindow("OpenGL window",
+ SDL_WINDOWPOS_UNDEFINED,
+ SDL_WINDOWPOS_UNDEFINED,
+ WIDTH, HEIGHT,
+ SDL_WINDOW_OPENGL);
+ SDL_GLContext context = SDL_GL_CreateContext(window);
+ assert(context != nullptr);
+
+ CHECK(init_movit("/usr/share/movit", MOVIT_DEBUG_OFF));
+
+ EffectChain chain(WIDTH, HEIGHT);
+ glViewport(0, 0, WIDTH, HEIGHT);
+
+ ImageFormat inout_format;
+ inout_format.color_space = COLORSPACE_sRGB;
+ inout_format.gamma_curve = GAMMA_sRGB;
+
+ Effect *last_effect = nullptr;
+ for (int i = 1; i < argc; ++i) {
+ fprintf(stderr, "%s...\n", argv[i]);
+ unsigned img_w, img_h;
+ unsigned char *src_img = load_image(argv[i], &img_w, &img_h);
+
+ FlatInput *input = new FlatInput(inout_format, FORMAT_BGRA_POSTMULTIPLIED_ALPHA, GL_UNSIGNED_BYTE, img_w, img_h);
+ input->set_pixel_data(src_img);
+ chain.add_input(input);
+
+ if (i == 1) {
+ last_effect = input;
+ } else {
+ Effect *mix_effect = chain.add_effect(new MixEffect(), last_effect, input);
+ float z = 1.0f / i;
+ CHECK(mix_effect->set_float("strength_first", 1.0f - z));
+ CHECK(mix_effect->set_float("strength_second", z));
+ last_effect = mix_effect;
+ }
+
+ if (i % 10 == 0) {
+ last_effect = chain.add_effect(new BouncingIdentityEffect());
+ }
+ }
+
+ chain.add_output(inout_format, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED);
+ chain.set_dither_bits(8);
+ chain.finalize();
+
+ // generate a PBO to hold the data we read back with glReadPixels()
+ // (Intel/DRI goes into a slow path if we don't read to PBO)
+ GLuint pbo;
+ glGenBuffers(1, &pbo);
+ glBindBuffer(GL_PIXEL_PACK_BUFFER_ARB, pbo);
+ glBufferData(GL_PIXEL_PACK_BUFFER_ARB, WIDTH * HEIGHT * 4, nullptr, GL_STREAM_READ);
+
+ chain.render_to_screen();
+
+ glBindBuffer(GL_PIXEL_PACK_BUFFER_ARB, pbo);
+ check_error();
+ glReadPixels(0, 0, WIDTH, HEIGHT, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, BUFFER_OFFSET(0));
+ check_error();
+
+ unsigned char *screenbuf = (unsigned char *)glMapBuffer(GL_PIXEL_PACK_BUFFER_ARB, GL_READ_ONLY);
+ write_png("blurout.png", screenbuf);
+
+ return 0;
+}