Merge CV, ver=5.1; ops/methods from HV, and interface from CV where possible
[goodguy/history.git] / cinelerra-5.1 / plugins / interpolatevideo / interpolatevideo.C
diff --git a/cinelerra-5.1/plugins/interpolatevideo/interpolatevideo.C b/cinelerra-5.1/plugins/interpolatevideo/interpolatevideo.C
new file mode 100644 (file)
index 0000000..c70d6ad
--- /dev/null
@@ -0,0 +1,1005 @@
+/*
+ * CINELERRA
+ * Copyright (C) 1997-2011 Adam Williams <broadcast at earthling dot net>
+ * 
+ * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ * 
+ */
+
+#include "bcsignals.h"
+#include "clip.h"
+#include "filexml.h"
+#include "interpolatevideo.h"
+#include "interpolatewindow.h"
+#include "language.h"
+#include "motionscan.h"
+#include "opticflow.h"
+#include "transportque.inc"
+#include <unistd.h>
+
+
+
+
+
+
+
+
+
+InterpolateVideoConfig::InterpolateVideoConfig()
+{
+       input_rate = (double)30000 / 1001;
+       use_keyframes = 0;
+       optic_flow = 1;
+       draw_vectors = 1;
+       search_radius = 16;
+       macroblock_size = 16;
+}
+
+void InterpolateVideoConfig::copy_from(InterpolateVideoConfig *config)
+{
+       this->input_rate = config->input_rate;
+       this->use_keyframes = config->use_keyframes;
+       this->optic_flow = config->optic_flow;
+       this->draw_vectors = config->draw_vectors;
+       this->search_radius = config->search_radius;
+       this->macroblock_size = config->macroblock_size;
+}
+
+int InterpolateVideoConfig::equivalent(InterpolateVideoConfig *config)
+{
+       return EQUIV(this->input_rate, config->input_rate) &&
+               (this->use_keyframes == config->use_keyframes) &&
+               this->optic_flow == config->optic_flow &&
+               this->draw_vectors == config->draw_vectors &&
+               this->search_radius == config->search_radius &&
+               this->macroblock_size == config->macroblock_size;
+}
+
+
+
+
+
+
+
+
+REGISTER_PLUGIN(InterpolateVideo)
+
+
+
+
+
+
+
+InterpolateVideo::InterpolateVideo(PluginServer *server)
+ : PluginVClient(server)
+{
+       optic_flow_engine = 0;
+       warp_engine = 0;
+       blend_engine = 0;
+       bzero(frames, sizeof(VFrame*) * 2);
+       for(int i = 0; i < 2; i++)
+               frame_number[i] = -1;
+       last_position = -1;
+       last_rate = -1;
+       last_macroblock_size = 0;
+       last_search_radius = 0;
+       total_macroblocks = 0;
+}
+
+
+InterpolateVideo::~InterpolateVideo()
+{
+       delete optic_flow_engine;
+       delete warp_engine;
+       delete blend_engine;
+       if(frames[0]) delete frames[0];
+       if(frames[1]) delete frames[1];
+       macroblocks.remove_all_objects();
+}
+
+
+void InterpolateVideo::fill_border(double frame_rate, int64_t start_position)
+{
+// A border frame changed or the start position is not identical to the last 
+// start position.
+
+       int64_t frame_start = range_start + (get_direction() == PLAY_REVERSE ? 1 : 0); 
+       int64_t frame_end = range_end + (get_direction() == PLAY_REVERSE ? 1 : 0); 
+
+       if( last_position == start_position && EQUIV(last_rate, frame_rate) &&
+               frame_number[0] >= 0 && frame_number[0] == frame_start &&
+               frame_number[1] >= 0 && frame_number[1] == frame_end ) return;
+
+       if( frame_start == frame_number[1] || frame_end == frame_number[0] )
+       {
+               int64_t n = frame_number[0];
+               frame_number[0] = frame_number[1];
+               frame_number[1] = n;
+               VFrame *f = frames[0];
+               frames[0] = frames[1];
+               frames[1] = f;
+       }
+
+       if( frame_start != frame_number[0] )
+       {
+//printf("InterpolateVideo::fill_border 1 %lld\n", range_start);
+               read_frame(frames[0], 0, frame_start, active_input_rate, 0);
+               frame_number[0] = frame_start;
+       }
+
+       if( frame_end != frame_number[1] )
+       {
+//printf("InterpolateVideo::fill_border 2 %lld\n", range_start);
+               read_frame(frames[1], 0, frame_end, active_input_rate, 0);
+               frame_number[1] = frame_end;
+       }
+
+       last_position = start_position;
+       last_rate = frame_rate;
+}
+
+
+
+
+
+void InterpolateVideo::draw_pixel(VFrame *frame, int x, int y)
+{
+       if(!(x >= 0 && y >= 0 && x < frame->get_w() && y < frame->get_h())) return;
+
+#define DRAW_PIXEL(x, y, components, do_yuv, max, type) \
+{ \
+       type **rows = (type**)frame->get_rows(); \
+       rows[y][x * components] = max - rows[y][x * components]; \
+       if(!do_yuv) \
+       { \
+               rows[y][x * components + 1] = max - rows[y][x * components + 1]; \
+               rows[y][x * components + 2] = max - rows[y][x * components + 2]; \
+       } \
+       else \
+       { \
+               rows[y][x * components + 1] = (max / 2 + 1) - rows[y][x * components + 1]; \
+               rows[y][x * components + 2] = (max / 2 + 1) - rows[y][x * components + 2]; \
+       } \
+       if(components == 4) \
+               rows[y][x * components + 3] = max; \
+}
+
+
+       switch(frame->get_color_model())
+       {
+               case BC_RGB888:
+                       DRAW_PIXEL(x, y, 3, 0, 0xff, unsigned char);
+                       break;
+               case BC_RGBA8888:
+                       DRAW_PIXEL(x, y, 4, 0, 0xff, unsigned char);
+                       break;
+               case BC_RGB_FLOAT:
+                       DRAW_PIXEL(x, y, 3, 0, 1.0, float);
+                       break;
+               case BC_RGBA_FLOAT:
+                       DRAW_PIXEL(x, y, 4, 0, 1.0, float);
+                       break;
+               case BC_YUV888:
+                       DRAW_PIXEL(x, y, 3, 1, 0xff, unsigned char);
+                       break;
+               case BC_YUVA8888:
+                       DRAW_PIXEL(x, y, 4, 1, 0xff, unsigned char);
+                       break;
+               case BC_RGB161616:
+                       DRAW_PIXEL(x, y, 3, 0, 0xffff, uint16_t);
+                       break;
+               case BC_YUV161616:
+                       DRAW_PIXEL(x, y, 3, 1, 0xffff, uint16_t);
+                       break;
+               case BC_RGBA16161616:
+                       DRAW_PIXEL(x, y, 4, 0, 0xffff, uint16_t);
+                       break;
+               case BC_YUVA16161616:
+                       DRAW_PIXEL(x, y, 4, 1, 0xffff, uint16_t);
+                       break;
+       }
+}
+
+
+void InterpolateVideo::draw_line(VFrame *frame, int x1, int y1, int x2, int y2)
+{
+       int w = labs(x2 - x1);
+       int h = labs(y2 - y1);
+//printf("InterpolateVideo::draw_line 1 %d %d %d %d\n", x1, y1, x2, y2);
+
+       if(!w && !h)
+       {
+               draw_pixel(frame, x1, y1);
+       }
+       else
+       if(w > h)
+       {
+// Flip coordinates so x1 < x2
+               if(x2 < x1)
+               {
+                       y2 ^= y1;
+                       y1 ^= y2;
+                       y2 ^= y1;
+                       x1 ^= x2;
+                       x2 ^= x1;
+                       x1 ^= x2;
+               }
+               int numerator = y2 - y1;
+               int denominator = x2 - x1;
+               for(int i = x1; i < x2; i++)
+               {
+                       int y = y1 + (int64_t)(i - x1) * (int64_t)numerator / (int64_t)denominator;
+                       draw_pixel(frame, i, y);
+               }
+       }
+       else
+       {
+// Flip coordinates so y1 < y2
+               if(y2 < y1)
+               {
+                       y2 ^= y1;
+                       y1 ^= y2;
+                       y2 ^= y1;
+                       x1 ^= x2;
+                       x2 ^= x1;
+                       x1 ^= x2;
+               }
+               int numerator = x2 - x1;
+               int denominator = y2 - y1;
+               for(int i = y1; i < y2; i++)
+               {
+                       int x = x1 + (int64_t)(i - y1) * (int64_t)numerator / (int64_t)denominator;
+                       draw_pixel(frame, x, i);
+               }
+       }
+//printf("InterpolateVideo::draw_line 2\n");
+}
+
+#define ARROW_SIZE 10
+void InterpolateVideo::draw_arrow(VFrame *frame, 
+       int x1, 
+       int y1, 
+       int x2, 
+       int y2)
+{
+       double angle = atan((float)(y2 - y1) / (float)(x2 - x1));
+       double angle1 = angle + (float)145 / 360 * 2 * 3.14159265;
+       double angle2 = angle - (float)145 / 360 * 2 * 3.14159265;
+       int x3;
+       int y3;
+       int x4;
+       int y4;
+       if(x2 < x1)
+       {
+               x3 = x2 - (int)(ARROW_SIZE * cos(angle1));
+               y3 = y2 - (int)(ARROW_SIZE * sin(angle1));
+               x4 = x2 - (int)(ARROW_SIZE * cos(angle2));
+               y4 = y2 - (int)(ARROW_SIZE * sin(angle2));
+       }
+       else
+       {
+               x3 = x2 + (int)(ARROW_SIZE * cos(angle1));
+               y3 = y2 + (int)(ARROW_SIZE * sin(angle1));
+               x4 = x2 + (int)(ARROW_SIZE * cos(angle2));
+               y4 = y2 + (int)(ARROW_SIZE * sin(angle2));
+       }
+
+// Main vector
+       draw_line(frame, x1, y1, x2, y2);
+//     draw_line(frame, x1, y1 + 1, x2, y2 + 1);
+
+// Arrow line
+       if(abs(y2 - y1) || abs(x2 - x1)) draw_line(frame, x2, y2, x3, y3);
+//     draw_line(frame, x2, y2 + 1, x3, y3 + 1);
+// Arrow line
+       if(abs(y2 - y1) || abs(x2 - x1)) draw_line(frame, x2, y2, x4, y4);
+//     draw_line(frame, x2, y2 + 1, x4, y4 + 1);
+}
+
+void InterpolateVideo::draw_rect(VFrame *frame, 
+       int x1, 
+       int y1, 
+       int x2, 
+       int y2)
+{
+       draw_line(frame, x1, y1, x2, y1);
+       draw_line(frame, x2, y1, x2, y2);
+       draw_line(frame, x2, y2, x1, y2);
+       draw_line(frame, x1, y2, x1, y1);
+}
+
+
+void InterpolateVideo::create_macroblocks()
+{
+// Get macroblock size
+       x_macroblocks = frames[0]->get_w() / config.macroblock_size;
+       y_macroblocks = frames[0]->get_h() / config.macroblock_size;
+
+       if(config.macroblock_size * x_macroblocks < frames[0]->get_w())
+       {
+               x_macroblocks++;
+       }
+       
+       if(config.macroblock_size * y_macroblocks < frames[0]->get_h())
+       {
+               y_macroblocks++;
+       }
+       
+       total_macroblocks = x_macroblocks * y_macroblocks;
+
+       if(total_macroblocks != macroblocks.size())
+       {
+               macroblocks.remove_all_objects();
+       }
+
+       for(int i = 0; i < total_macroblocks; i++)
+       {
+               OpticFlowMacroblock *mb = 0;
+               if(macroblocks.size() > i) 
+               {
+                       mb = macroblocks.get(i);
+               }
+               else
+               {
+                       mb = new OpticFlowMacroblock;
+                       macroblocks.append(mb);
+               }
+
+               mb->x = (i % x_macroblocks) * config.macroblock_size + config.macroblock_size / 2;
+               mb->y = (i / x_macroblocks) * config.macroblock_size + config.macroblock_size / 2;
+       }
+}
+
+
+void InterpolateVideo::draw_vectors(int processed)
+{
+// Draw arrows
+       if(config.draw_vectors)
+       {
+               create_macroblocks();
+
+               for(int i = 0; i < total_macroblocks; i++)
+               {
+                       OpticFlowMacroblock *mb = macroblocks.get(i);
+//                 printf("InterpolateVideo::optic_flow %d x=%d y=%d dx=%d dy=%d\n",
+//                         __LINE__,
+//                         mb->x,
+//                         mb->y,
+//                         mb->dx / OVERSAMPLE,
+//                         mb->dy / OVERSAMPLE);
+
+                       if(processed)
+                       {
+                               draw_arrow(get_output(),
+                                       mb->x,
+                                       mb->y,
+                                       mb->x - mb->dx / OVERSAMPLE,
+                                       mb->y - mb->dy / OVERSAMPLE);
+
+// debug
+//                             if(mb->is_valid && mb->visible)
+//                             {
+//                                     draw_arrow(get_output(),
+//                                             mb->x + 1,
+//                                             mb->y + 1,
+//                                             mb->x - mb->dx / OVERSAMPLE + 1,
+//                                             mb->y - mb->dy / OVERSAMPLE + 1);
+//                             }
+                       }
+                       else
+                       {
+                               draw_pixel(get_output(),
+                                       mb->x,
+                                       mb->y);
+                       }
+               }
+               
+// Draw center macroblock
+               OpticFlowMacroblock *mb = macroblocks.get(
+                       x_macroblocks / 2 + y_macroblocks / 2 * x_macroblocks);
+               draw_rect(get_output(),
+                       mb->x - config.macroblock_size / 2,
+                       mb->y - config.macroblock_size / 2,
+                       mb->x + config.macroblock_size / 2,
+                       mb->y + config.macroblock_size / 2);
+               draw_rect(get_output(),
+                       mb->x - config.macroblock_size / 2 - config.search_radius,
+                       mb->y - config.macroblock_size / 2 - config.search_radius,
+                       mb->x + config.macroblock_size / 2 + config.search_radius,
+                       mb->y + config.macroblock_size / 2 + config.search_radius);
+       }
+}
+
+
+int InterpolateVideo::angles_overlap(float dst2_angle1, 
+       float dst2_angle2,
+       float dst1_angle1,
+       float dst1_angle2)
+{
+       if(dst2_angle1 < 0 || dst2_angle2 < 0)
+       {
+               dst2_angle1 += 2 * M_PI;
+               dst2_angle2 += 2 * M_PI;
+       }
+
+       if(dst1_angle1 < 0 || dst1_angle2 < 0)
+       {
+               dst1_angle1 += 2 * M_PI;
+               dst1_angle2 += 2 * M_PI;
+       }
+
+       if(dst1_angle1 < dst2_angle2 && 
+               dst1_angle2 > dst2_angle1) return 1;
+
+       return 0;
+}
+
+
+
+void InterpolateVideo::blend_macroblock(int number)
+{
+       OpticFlowMacroblock *src = macroblocks.get(number);
+       struct timeval start_time;
+       gettimeofday(&start_time, 0);
+//     printf("InterpolateVideo::blend_macroblock %d %d\n",
+//     __LINE__,
+//     src->is_valid);
+
+
+// Copy macroblock table to local thread
+       ArrayList<OpticFlowMacroblock*> local_macroblocks;
+       for(int i = 0; i < macroblocks.size(); i++)
+       {
+               OpticFlowMacroblock *mb = new OpticFlowMacroblock;
+               mb->copy_from(macroblocks.get(i));
+               local_macroblocks.append(mb);
+       }
+
+// Get nearest macroblocks
+       for(int i = 0; i < local_macroblocks.size(); i++)
+       {
+               OpticFlowMacroblock *dst = local_macroblocks.get(i);
+               if(i != number && dst->is_valid)
+               {
+
+// rough estimation of angle coverage
+                       float angle = atan2(dst->y - src->y, dst->x - src->x);
+                       float dist = sqrt(SQR(dst->y - src->y) + SQR(dst->x - src->x));
+                       float span = sin((float)config.macroblock_size / dist);
+                       dst->angle1 = angle - span / 2;
+                       dst->angle2 = angle + span / 2;
+                       dst->dist = dist;
+// All macroblocks start as visible
+                       dst->visible = 1;
+
+// printf("InterpolateVideo::blend_macroblock %d %d x=%d y=%d span=%f angle1=%f angle2=%f dist=%f\n", 
+// __LINE__, 
+// i,
+// dst->x, 
+// dst->y, 
+// span * 360 / 2 / M_PI,
+// dst->angle1 * 360 / 2 / M_PI, 
+// dst->angle2 * 360 / 2 / M_PI,
+// dst->dist);
+               }
+       }
+       
+       for(int i = 0; i < local_macroblocks.size(); i++)
+       {
+// Conceil macroblocks which are hidden
+               OpticFlowMacroblock *dst1 = local_macroblocks.get(i);
+               if(i != number && dst1->is_valid && dst1->visible)
+               {
+// Find macroblock which is obstructing
+                       for(int j = 0; j < local_macroblocks.size(); j++)
+                       {
+                               OpticFlowMacroblock *dst2 = local_macroblocks.get(j);
+                               if(j != number && 
+                                       dst2->is_valid && 
+                                       dst2->dist < dst1->dist &&
+                                       angles_overlap(dst2->angle1, 
+                                               dst2->angle2,
+                                               dst1->angle1,
+                                               dst1->angle2))
+                               {
+                                       dst1->visible = 0;
+                                       j = local_macroblocks.size();
+                               }
+                       }
+               }
+       }
+
+// Blend all visible macroblocks
+// Get distance metrics
+       float total = 0;
+       float min = 0;
+       float max = 0;
+       int first = 1;
+       for(int i = 0; i < local_macroblocks.size(); i++)
+       {
+               OpticFlowMacroblock *dst = local_macroblocks.get(i);
+               if(i != number && dst->is_valid && dst->visible)
+               {
+                       total += dst->dist;
+                       if(first)
+                       {
+                               min = max = dst->dist;
+                               first = 0;
+                       }
+                       else
+                       {
+                               min = MIN(dst->dist, min);
+                               max = MAX(dst->dist, max);
+                       }
+// printf("InterpolateVideo::blend_macroblock %d %d x=%d y=%d dist=%f\n", 
+// __LINE__, 
+// i,
+// dst->x, 
+// dst->y, 
+// dst->dist);
+
+               }
+       }
+
+// Invert distances to convert to weights
+       total = 0;
+       for(int i = 0; i < local_macroblocks.size(); i++)
+       {
+               OpticFlowMacroblock *dst = local_macroblocks.get(i);
+               if(i != number && dst->is_valid && dst->visible)
+               {
+                       dst->dist = max - dst->dist + min;
+                       total += dst->dist;
+// printf("InterpolateVideo::blend_macroblock %d %d x=%d y=%d dist=%f\n", 
+// __LINE__, 
+// i,
+// dst->x, 
+// dst->y, 
+// max - dst->dist + min);
+
+               }
+       }
+
+// Add weighted vectors
+       float dx = 0;
+       float dy = 0;
+       if(total > 0)
+       {
+               for(int i = 0; i < local_macroblocks.size(); i++)
+               {
+                       OpticFlowMacroblock *dst = local_macroblocks.get(i);
+                       if(i != number && dst->is_valid && dst->visible)
+                       {
+                               dx += dst->dist * dst->dx / total;
+                               dy += dst->dist * dst->dy / total;
+                               src->dx = dx;
+                               src->dy = dy;
+// printf("InterpolateVideo::blend_macroblock %d %d x=%d y=%d dist=%f\n", 
+// __LINE__, 
+// i,
+// dst->x, 
+// dst->y, 
+// max - dst->dist + min);
+
+                       }
+               }
+       }
+
+       local_macroblocks.remove_all_objects();
+       
+// printf("InterpolateVideo::blend_macroblock %d total=%f\n", 
+// __LINE__, 
+// total);
+       struct timeval end_time;
+       gettimeofday(&end_time, 0);
+//     printf("InterpolateVideo::blend_macroblock %d %d\n",
+//             __LINE__,
+//             end_time.tv_sec * 1000 + end_time.tv_usec / 1000 -
+//             start_time.tv_sec * 1000 - start_time.tv_usec / 1000);
+}
+
+
+
+void InterpolateVideo::optic_flow()
+{
+
+       create_macroblocks();
+       int need_motion = 0;
+
+// New engine
+       if(!optic_flow_engine)
+       {
+               optic_flow_engine = new OpticFlow(this,
+                       PluginClient::get_project_smp() + 1, 
+                       PluginClient::get_project_smp() + 1);
+               need_motion = 1;
+       }
+       else
+// Reuse old vectors
+       if(motion_number[0] == frame_number[0] &&
+               motion_number[1] == frame_number[1] &&
+               last_macroblock_size == config.macroblock_size &&
+               last_search_radius == config.search_radius)
+       {
+               ;
+       }
+       else
+// Calculate new vectors
+       {
+               need_motion = 1;
+       }
+
+       if(need_motion)
+       {
+               optic_flow_engine->set_package_count(MIN(MAX_PACKAGES, total_macroblocks));
+               optic_flow_engine->process_packages();
+
+// Fill in failed macroblocks
+               invalid_blocks.remove_all();
+               for(int i = 0; i < macroblocks.size(); i++)
+               {
+                       if(!macroblocks.get(i)->is_valid 
+// debug
+//                              && i >= 30 * x_macroblocks)
+                               )
+                       {
+                               invalid_blocks.append(i);
+                       }
+               }
+
+               if(invalid_blocks.size())
+               {
+                       if(!blend_engine)
+                       {
+                               blend_engine = new BlendMacroblock(this,
+                                       PluginClient::get_project_smp() + 1, 
+                                       PluginClient::get_project_smp() + 1);
+                       }
+
+                       blend_engine->set_package_count(MIN(PluginClient::get_project_smp() + 1, 
+                               invalid_blocks.size()));
+                       blend_engine->process_packages();
+               }
+       }
+
+
+
+// for(int i = 0; i < total_macroblocks; i++)
+// {
+//     OpticFlowPackage *pkg = (OpticFlowPackage*)optic_flow_engine->get_package(
+//             i);
+//     if((i / x_macroblocks) % 2)
+//     {
+//             pkg->dx = 0;
+//             pkg->dy = 0;
+//     }
+//     else
+//     {
+//             pkg->dx = -32;
+//             pkg->dy = 0;
+//     }
+// }
+
+
+       if(!warp_engine)
+       {
+               warp_engine = new Warp(this,
+                       PluginClient::get_project_smp() + 1, 
+                       PluginClient::get_project_smp() + 1);
+       }
+       
+       warp_engine->process_packages();
+
+       motion_number[0] = frame_number[0];
+       motion_number[1] = frame_number[1];
+       last_macroblock_size = config.macroblock_size;
+       last_search_radius = config.search_radius;
+
+
+// Debug
+//     get_output()->copy_from(frames[1]);
+
+
+       draw_vectors(1);
+}
+
+
+#define AVERAGE(type, temp_type,components, max) \
+{ \
+       temp_type fraction0 = (temp_type)(lowest_fraction * max); \
+       temp_type fraction1 = (temp_type)(max - fraction0); \
+ \
+       for(int i = 0; i < h; i++) \
+       { \
+               type *prev_row0 = (type*)frames[0]->get_rows()[i]; \
+               type *next_row0 = (type*)frames[1]->get_rows()[i]; \
+               type *out_row = (type*)frame->get_rows()[i]; \
+               for(int j = 0; j < w * components; j++) \
+               { \
+                       *out_row++ = (*prev_row0++ * fraction0 + *next_row0++ * fraction1) / max; \
+               } \
+       } \
+}
+
+
+void InterpolateVideo::average()
+{
+       VFrame *frame = get_output();
+       int w = frame->get_w();
+       int h = frame->get_h();
+
+       switch(frame->get_color_model())
+       {
+               case BC_RGB_FLOAT:
+                       AVERAGE(float, float, 3, 1);
+                       break;
+               case BC_RGB888:
+               case BC_YUV888:
+                       AVERAGE(unsigned char, int, 3, 0xff);
+                       break;
+               case BC_RGBA_FLOAT:
+                       AVERAGE(float, float, 4, 1);
+                       break;
+               case BC_RGBA8888:
+               case BC_YUVA8888:
+                       AVERAGE(unsigned char, int, 4, 0xff);
+                       break;
+       }
+}
+
+
+int InterpolateVideo::process_buffer(VFrame *frame,
+       int64_t start_position,
+       double frame_rate)
+{
+       if(get_direction() == PLAY_REVERSE) start_position--;
+       load_configuration();
+
+       if(!frames[0])
+       {
+               for(int i = 0; i < 2; i++)
+               {
+                       frames[i] = new VFrame(0,
+                               -1,
+                               frame->get_w(),
+                               frame->get_h(),
+                               frame->get_color_model(),
+                               -1);
+               }
+       }
+//printf("InterpolateVideo::process_buffer 1 %lld %lld\n", range_start, range_end);
+
+// Fraction of lowest frame in output
+       int64_t requested_range_start = (int64_t)((double)range_start * 
+               frame_rate / active_input_rate);
+       int64_t requested_range_end = (int64_t)((double)range_end * 
+               frame_rate / active_input_rate);
+       if(requested_range_start == requested_range_end)
+       {
+               read_frame(frame, 0, range_start, active_input_rate, 0);
+        }
+        else
+        {
+
+// Fill border frames
+               fill_border(frame_rate, start_position);
+
+               float highest_fraction = (float)(start_position - requested_range_start) /
+                       (requested_range_end - requested_range_start);
+
+// Fraction of highest frame in output
+               lowest_fraction = 1.0 - highest_fraction;
+
+               CLAMP(highest_fraction, 0, 1);
+               CLAMP(lowest_fraction, 0, 1);
+
+// printf("InterpolateVideo::process_buffer %lld %lld %lld %f %f %lld %lld %f %f\n",
+// range_start,
+// range_end,
+// requested_range_start,
+// requested_range_end,
+// start_position,
+// config.input_rate,
+// frame_rate,
+// lowest_fraction,
+// highest_fraction);
+
+               if(start_position == (int64_t)(range_start * frame_rate / active_input_rate))
+               {
+//printf("InterpolateVideo::process_buffer %d\n", __LINE__);
+                       frame->copy_from(frames[0]);
+                       
+                       if(config.optic_flow)
+                       {
+                               draw_vectors(0);
+                       }
+               }
+               else
+               if(config.optic_flow)
+               {
+//printf("InterpolateVideo::process_buffer %d\n", __LINE__);
+                       optic_flow();
+               }
+               else
+               {
+                       average();
+               }
+       }
+       return 0;
+}
+
+
+
+
+NEW_WINDOW_MACRO(InterpolateVideo, InterpolateVideoWindow)
+const char* InterpolateVideo::plugin_title() { return _("Interpolate Video"); }
+int InterpolateVideo::is_realtime() { return 1; }
+
+int InterpolateVideo::load_configuration()
+{
+       KeyFrame *prev_keyframe, *next_keyframe;
+       InterpolateVideoConfig old_config;
+       old_config.copy_from(&config);
+
+       next_keyframe = get_next_keyframe(get_source_position());
+       prev_keyframe = get_prev_keyframe(get_source_position());
+// Previous keyframe stays in config object.
+       read_data(prev_keyframe);
+
+
+       int64_t prev_position = edl_to_local(prev_keyframe->position);
+       int64_t next_position = edl_to_local(next_keyframe->position);
+       if(prev_position == 0 && next_position == 0)
+       {
+               next_position = prev_position = get_source_start();
+       }
+// printf("InterpolateVideo::load_configuration 1 %lld %lld %lld %lld\n",
+// prev_keyframe->position,
+// next_keyframe->position,
+// prev_position,
+// next_position);
+
+// Get range to average in requested rate
+       range_start = prev_position;
+       range_end = next_position;
+
+
+// Use keyframes to determine range
+       if(config.use_keyframes)
+       {
+               active_input_rate = get_framerate();
+// Between keyframe and edge of range or no keyframes
+               if(range_start == range_end)
+               {
+// Between first keyframe and start of effect
+                       if(get_source_position() >= get_source_start() &&
+                               get_source_position() < range_start)
+                       {
+                               range_start = get_source_start();
+                       }
+                       else
+// Between last keyframe and end of effect
+                       if(get_source_position() >= range_start &&
+                               get_source_position() < get_source_start() + get_total_len())
+                       {
+// Last frame should be inclusive of current effect
+                               range_end = get_source_start() + get_total_len() - 1;
+                       }
+                       else
+                       {
+// Should never get here
+                               ;
+                       }
+               }
+
+
+// Make requested rate equal to input rate for this mode.
+
+// Convert requested rate to input rate
+// printf("InterpolateVideo::load_configuration 2 %lld %lld %f %f\n", 
+// range_start, 
+// range_end,
+// get_framerate(),
+// config.input_rate);
+//             range_start = (int64_t)((double)range_start / get_framerate() * active_input_rate + 0.5);
+//             range_end = (int64_t)((double)range_end / get_framerate() * active_input_rate + 0.5);
+       }
+       else
+// Use frame rate
+       {
+               active_input_rate = config.input_rate;
+// Convert to input frame rate
+               range_start = (int64_t)(get_source_position() / 
+                       get_framerate() *
+                       active_input_rate);
+               range_end = (int64_t)(get_source_position() / 
+                       get_framerate() *
+                       active_input_rate) + 1;
+       }
+
+// printf("InterpolateVideo::load_configuration 1 %lld %lld %lld %lld %lld %lld\n",
+// prev_keyframe->position,
+// next_keyframe->position,
+// prev_position,
+// next_position,
+// range_start,
+// range_end);
+
+
+       return !config.equivalent(&old_config);
+}
+
+
+void InterpolateVideo::save_data(KeyFrame *keyframe)
+{
+       FileXML output;
+
+// cause data to be stored directly in text
+       output.set_shared_output(keyframe->get_data(), MESSAGESIZE);
+       output.tag.set_title("INTERPOLATEVIDEO");
+       output.tag.set_property("INPUT_RATE", config.input_rate);
+       output.tag.set_property("USE_KEYFRAMES", config.use_keyframes);
+       output.tag.set_property("OPTIC_FLOW", config.optic_flow);
+       output.tag.set_property("DRAW_VECTORS", config.draw_vectors);
+       output.tag.set_property("SEARCH_RADIUS", config.search_radius);
+       output.tag.set_property("MACROBLOCK_SIZE", config.macroblock_size);
+       output.append_tag();
+       output.tag.set_title("/INTERPOLATEVIDEO");
+       output.append_tag();
+       output.append_newline();
+       output.terminate_string();
+}
+
+void InterpolateVideo::read_data(KeyFrame *keyframe)
+{
+       FileXML input;
+
+       input.set_shared_input(keyframe->get_data(), strlen(keyframe->get_data()));
+
+       while(!input.read_tag())
+       {
+               if(input.tag.title_is("INTERPOLATEVIDEO"))
+               {
+                       config.input_rate = input.tag.get_property("INPUT_RATE", config.input_rate);
+                       config.input_rate = Units::fix_framerate(config.input_rate);
+                       config.use_keyframes = input.tag.get_property("USE_KEYFRAMES", config.use_keyframes);
+                       config.optic_flow = input.tag.get_property("OPTIC_FLOW", config.optic_flow);
+                       config.draw_vectors = input.tag.get_property("DRAW_VECTORS", config.draw_vectors);
+                       config.search_radius = input.tag.get_property("SEARCH_RADIUS", config.search_radius);
+                       config.macroblock_size = input.tag.get_property("MACROBLOCK_SIZE", config.macroblock_size);
+               }
+       }
+}
+
+void InterpolateVideo::update_gui()
+{
+       if(thread)
+       {
+               if(load_configuration())
+               {
+                       thread->window->lock_window("InterpolateVideo::update_gui");
+                       ((InterpolateVideoWindow*)thread->window)->rate->update((float)config.input_rate);
+                       ((InterpolateVideoWindow*)thread->window)->keyframes->update(config.use_keyframes);
+                       ((InterpolateVideoWindow*)thread->window)->flow->update(config.optic_flow);
+                       ((InterpolateVideoWindow*)thread->window)->vectors->update(config.draw_vectors);
+                       ((InterpolateVideoWindow*)thread->window)->radius->update(config.search_radius);
+                       ((InterpolateVideoWindow*)thread->window)->size->update(config.macroblock_size);
+                       ((InterpolateVideoWindow*)thread->window)->update_enabled();
+                       thread->window->unlock_window();
+               }
+       }
+}
+
+