dynamic keyframes, textbox rework, andrea ffmpeg.opts, perpetual chkpt undo, lv2...
[goodguy/history.git] / cinelerra-5.1 / plugins / perspective / perspective.C
1
2 /*
3  * CINELERRA
4  * Copyright (C) 2008 Adam Williams <broadcast at earthling dot net>
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19  *
20  */
21
22 #include "affine.h"
23 #include "cursors.h"
24 #include "language.h"
25 #include "perspective.h"
26
27 #define PERSPECTIVE_WIDTH 400
28 #define PERSPECTIVE_HEIGHT 600
29
30 REGISTER_PLUGIN(PerspectiveMain)
31
32
33 PerspectiveConfig::PerspectiveConfig()
34 {
35         x1 = 0;    y1 = 0;
36         x2 = 100;  y2 = 0;
37         x3 = 100;  y3 = 100;
38         x4 = 0;    y4 = 100;
39         mode = AffineEngine::PERSPECTIVE;
40         smoothing = AffineEngine::AF_DEFAULT;
41         window_w = PERSPECTIVE_WIDTH;
42         window_h = PERSPECTIVE_HEIGHT;
43         current_point = 0;
44         forward = 1;
45         view_x = view_y = 0;
46         view_zoom = 1;
47 }
48
49 int PerspectiveConfig::equivalent(PerspectiveConfig &that)
50 {
51         return
52                 EQUIV(x1, that.x1) &&
53                 EQUIV(y1, that.y1) &&
54                 EQUIV(x2, that.x2) &&
55                 EQUIV(y2, that.y2) &&
56                 EQUIV(x3, that.x3) &&
57                 EQUIV(y3, that.y3) &&
58                 EQUIV(x4, that.x4) &&
59                 EQUIV(y4, that.y4) &&
60                 mode == that.mode &&
61                 smoothing == that.smoothing &&
62                 forward == that.forward;
63 // not tracking:
64 //              view_x == that.view_x &&
65 //              view_y == that.view_y &&
66 //              view_zoom == that.view_zoom &&
67 }
68
69 void PerspectiveConfig::copy_from(PerspectiveConfig &that)
70 {
71         x1 = that.x1;  y1 = that.y1;
72         x2 = that.x2;  y2 = that.y2;
73         x3 = that.x3;  y3 = that.y3;
74         x4 = that.x4;  y4 = that.y4;
75         mode = that.mode;
76         smoothing = that.smoothing;
77         window_w = that.window_w;
78         window_h = that.window_h;
79         current_point = that.current_point;
80         forward = that.forward;
81         view_x = that.view_x;
82         view_y = that.view_y;
83         view_zoom = that.view_zoom;
84 }
85
86 void PerspectiveConfig::interpolate(PerspectiveConfig &prev, PerspectiveConfig &next,
87         int64_t prev_frame, int64_t next_frame, int64_t current_frame)
88 {
89         double next_scale = (double)(current_frame - prev_frame) / (next_frame - prev_frame);
90         double prev_scale = (double)(next_frame - current_frame) / (next_frame - prev_frame);
91         this->x1 = prev.x1 * prev_scale + next.x1 * next_scale;
92         this->y1 = prev.y1 * prev_scale + next.y1 * next_scale;
93         this->x2 = prev.x2 * prev_scale + next.x2 * next_scale;
94         this->y2 = prev.y2 * prev_scale + next.y2 * next_scale;
95         this->x3 = prev.x3 * prev_scale + next.x3 * next_scale;
96         this->y3 = prev.y3 * prev_scale + next.y3 * next_scale;
97         this->x4 = prev.x4 * prev_scale + next.x4 * next_scale;
98         this->y4 = prev.y4 * prev_scale + next.y4 * next_scale;
99         mode = prev.mode;
100         smoothing = prev.smoothing;
101         forward = prev.forward;
102         view_x = prev.view_x;
103         view_y = prev.view_y;
104         view_zoom = prev.view_zoom;
105 }
106
107
108 PerspectiveWindow::PerspectiveWindow(PerspectiveMain *plugin)
109  : PluginClientWindow(plugin,
110         plugin->config.window_w, plugin->config.window_h,
111         plugin->config.window_w, plugin->config.window_h, 0)
112 {
113 //printf("PerspectiveWindow::PerspectiveWindow 1 %d %d\n", plugin->config.window_w, plugin->config.window_h);
114         this->plugin = plugin;
115 }
116
117 PerspectiveWindow::~PerspectiveWindow()
118 {
119 }
120
121 void PerspectiveWindow::create_objects()
122 {
123         int x = 10, y = 10;
124
125         add_subwindow(canvas = new PerspectiveCanvas(this,
126                 x, y, get_w() - 20, get_h() - 290));
127         canvas->set_cursor(CROSS_CURSOR, 0, 0);
128         y += canvas->get_h() + 10;
129         add_subwindow(new BC_Title(x, y, _("Current X:")));
130         x += 80;
131         this->x = new PerspectiveCoord(this,
132                 x, y, plugin->get_current_x(), 1);
133         this->x->create_objects();
134         x += 140;
135         add_subwindow(new BC_Title(x, y, _("Y:")));
136         x += 20;
137         this->y = new PerspectiveCoord(this,
138                 x, y, plugin->get_current_y(), 0);
139         this->y->create_objects();
140         x = 10;   y += 30;
141         add_subwindow(mode_perspective = new PerspectiveMode(this,
142                 x, y, AffineEngine::PERSPECTIVE, _("Perspective")));
143         x += 120;
144         add_subwindow(mode_sheer = new PerspectiveMode(this,
145                 x, y, AffineEngine::SHEER, _("Sheer")));
146         x += 100;
147         add_subwindow(affine = new PerspectiveAffine(this, x, y));
148         affine->create_objects();
149         x = 10;  y += 30;
150         add_subwindow(mode_stretch = new PerspectiveMode(this,
151                 x, y, AffineEngine::STRETCH, _("Stretch")));
152         x += 120;
153         add_subwindow(new PerspectiveReset(this, x, y));
154         update_canvas();
155
156         x = 10;   y += 30;
157         BC_Title *title;
158         add_subwindow(title = new BC_Title(x, y, _("Zoom view:")));
159         int x1 = x + title->get_w() + 10, w1 = get_w() - x1 - 10;
160         add_subwindow(zoom_view = new PerspectiveZoomView(this, x1, y, w1));
161         y += 30;
162
163         add_subwindow(new BC_Title(x, y, _("Perspective direction:")));
164         x += 170;
165         add_subwindow(forward = new PerspectiveDirection(this,
166                 x, y, 1, _("Forward")));
167         x += 100;
168         add_subwindow(reverse = new PerspectiveDirection(this,
169                 x, y, 0, _("Reverse")));
170         x = 10;  y += 40;
171         add_subwindow(title = new BC_Title(x, y, _("Alt/Shift:")));
172         add_subwindow(new BC_Title(x+100, y, _("Button1 Action:")));
173         y += title->get_h() + 5;
174         add_subwindow(new BC_Title(x, y,
175                 "  0 / 0\n"
176                 "  0 / 1\n"
177                 "  1 / 0\n"
178                 "  1 / 1"));
179         add_subwindow(new BC_Title(x+100, y,
180               _("Translate endpoint\n"
181                 "Zoom image\n"
182                 "Translate image\n"
183                 "Translate view")));
184         show_window();
185 }
186
187
188 int PerspectiveWindow::resize_event(int w, int h)
189 {
190         return 1;
191 }
192
193 PerspectiveZoomView::PerspectiveZoomView(PerspectiveWindow *gui,
194         int x, int y, int w)
195  : BC_FSlider(x, y, 0, w, w, -2., 2.,
196         log10(gui->plugin->config.view_zoom < 0.01 ?
197                 0.01 : gui->plugin->config.view_zoom))
198 {
199         this->gui = gui;
200         set_precision(0.001);
201         set_tooltip(_("Zoom"));
202 }
203 PerspectiveZoomView::~PerspectiveZoomView()
204 {
205 }
206 int PerspectiveZoomView::handle_event()
207 {
208         float value = get_value();
209         BC_FSlider::update(value);
210         PerspectiveMain *plugin = gui->plugin;
211         float view_zoom = plugin->config.view_zoom;
212         double new_zoom = pow(10.,value);
213         double scale = new_zoom / view_zoom;
214         plugin->config.view_zoom = new_zoom;
215         plugin->config.view_x *= scale;
216         plugin->config.view_y *= scale;
217         gui->update_canvas();
218         plugin->send_configure_change();
219         return 1;
220 }
221
222 char *PerspectiveZoomView::get_caption()
223 {
224         double value = get_value();
225         int frac = value >= 0. ? 1 : value >= -1. ? 2 : 3;
226         double zoom = pow(10., value);
227         char *caption = BC_Slider::get_caption();
228         sprintf(caption, "%.*f", frac, zoom);
229         return caption;
230 }
231
232 void PerspectiveZoomView::update(float zoom)
233 {
234         if( zoom < 0.01 ) zoom = 0.01;
235         float value = log10f(zoom);
236         BC_FSlider::update(value);
237 }
238
239 void PerspectiveWindow::update_canvas()
240 {
241         int cw = canvas->get_w(), ch = canvas->get_h();
242         canvas->clear_box(0, 0, cw, ch);
243         int x1, y1, x2, y2, x3, y3, x4, y4;
244         calculate_canvas_coords(x1, y1, x2, y2, x3, y3, x4, y4);
245         float x0 = cw / 2.0f, y0 = ch / 2.0f;
246         float view_zoom = plugin->config.view_zoom;
247         float view_x = plugin->config.view_x, view_y = plugin->config.view_y;
248         x1 = (x1-x0) * view_zoom + view_x + x0;
249         y1 = (y1-y0) * view_zoom + view_y + y0;
250         x2 = (x2-x0) * view_zoom + view_x + x0;
251         y2 = (y2-y0) * view_zoom + view_y + y0;
252         x3 = (x3-x0) * view_zoom + view_x + x0;
253         y3 = (y3-y0) * view_zoom + view_y + y0;
254         x4 = (x4-x0) * view_zoom + view_x + x0;
255         y4 = (y4-y0) * view_zoom + view_y + y0;
256
257         canvas->set_color(RED);
258         int vx1 = x0 - x0 * view_zoom + view_x;
259         int vy1 = y0 - y0 * view_zoom + view_y;
260         int vx2 = vx1 + cw * view_zoom - 1;
261         int vy2 = vy1 + ch * view_zoom - 1;
262         canvas->draw_line(vx1, vy1, vx2, vy1);
263         canvas->draw_line(vx2, vy1, vx2, vy2);
264         canvas->draw_line(vx2, vy2, vx1, vy2);
265         canvas->draw_line(vx1, vy2, vx1, vy1);
266
267 //printf("PerspectiveWindow::update_canvas %d,%d %d,%d %d,%d %d,%d\n",
268 // x1, y1, x2, y2, x3, y3, x4, y4);
269 // Draw divisions
270         canvas->set_color(WHITE);
271
272 #define DIVISIONS 10
273         for( int i=0; i<=DIVISIONS; ++i ) {
274                 canvas->draw_line( // latitude
275                         x1 + (x4 - x1) * i / DIVISIONS,
276                         y1 + (y4 - y1) * i / DIVISIONS,
277                         x2 + (x3 - x2) * i / DIVISIONS,
278                         y2 + (y3 - y2) * i / DIVISIONS);
279                 canvas->draw_line( // longitude
280                         x1 + (x2 - x1) * i / DIVISIONS,
281                         y1 + (y2 - y1) * i / DIVISIONS,
282                         x4 + (x3 - x4) * i / DIVISIONS,
283                         y4 + (y3 - y4) * i / DIVISIONS);
284         }
285
286 // Corners
287 #define RADIUS 5
288         if(plugin->config.current_point == 0)
289                 canvas->draw_disc(x1 - RADIUS, y1 - RADIUS, RADIUS * 2, RADIUS * 2);
290         else
291                 canvas->draw_circle(x1 - RADIUS, y1 - RADIUS, RADIUS * 2, RADIUS * 2);
292
293         if(plugin->config.current_point == 1)
294                 canvas->draw_disc(x2 - RADIUS, y2 - RADIUS, RADIUS * 2, RADIUS * 2);
295         else
296                 canvas->draw_circle(x2 - RADIUS, y2 - RADIUS, RADIUS * 2, RADIUS * 2);
297
298         if(plugin->config.current_point == 2)
299                 canvas->draw_disc(x3 - RADIUS, y3 - RADIUS, RADIUS * 2, RADIUS * 2);
300         else
301                 canvas->draw_circle(x3 - RADIUS, y3 - RADIUS, RADIUS * 2, RADIUS * 2);
302
303         if(plugin->config.current_point == 3)
304                 canvas->draw_disc(x4 - RADIUS, y4 - RADIUS, RADIUS * 2, RADIUS * 2);
305         else
306                 canvas->draw_circle(x4 - RADIUS, y4 - RADIUS, RADIUS * 2, RADIUS * 2);
307
308         canvas->flash();
309 }
310
311 void PerspectiveWindow::update_mode()
312 {
313         mode_perspective->update(plugin->config.mode == AffineEngine::PERSPECTIVE);
314         mode_sheer->update(plugin->config.mode == AffineEngine::SHEER);
315         mode_stretch->update(plugin->config.mode == AffineEngine::STRETCH);
316         forward->update(plugin->config.forward);
317         reverse->update(!plugin->config.forward);
318 }
319
320 void PerspectiveWindow::update_coord()
321 {
322         x->update(plugin->get_current_x());
323         y->update(plugin->get_current_y());
324 }
325
326 void PerspectiveWindow::update_view_zoom()
327 {
328         zoom_view->update(plugin->config.view_zoom);
329 }
330
331 void PerspectiveWindow::reset_view()
332 {
333         plugin->config.view_x = plugin->config.view_y = 0;
334         zoom_view->update(plugin->config.view_zoom = 1);
335         update_canvas();
336         plugin->send_configure_change();
337 }
338
339 void PerspectiveWindow::calculate_canvas_coords(
340         int &x1, int &y1, int &x2, int &y2,
341         int &x3, int &y3, int &x4, int &y4)
342 {
343         int w = canvas->get_w() - 1;
344         int h = canvas->get_h() - 1;
345         if( plugin->config.mode == AffineEngine::PERSPECTIVE ||
346             plugin->config.mode == AffineEngine::STRETCH ) {
347                 x1 = (int)(plugin->config.x1 * w / 100);
348                 y1 = (int)(plugin->config.y1 * h / 100);
349                 x2 = (int)(plugin->config.x2 * w / 100);
350                 y2 = (int)(plugin->config.y2 * h / 100);
351                 x3 = (int)(plugin->config.x3 * w / 100);
352                 y3 = (int)(plugin->config.y3 * h / 100);
353                 x4 = (int)(plugin->config.x4 * w / 100);
354                 y4 = (int)(plugin->config.y4 * h / 100);
355         }
356         else {
357                 x1 = (int)(plugin->config.x1 * w) / 100;
358                 y1 = 0;
359                 x2 = x1 + w;
360                 y2 = 0;
361                 x4 = (int)(plugin->config.x4 * w) / 100;
362                 y4 = h;
363                 x3 = x4 + w;
364                 y3 = h;
365         }
366 }
367
368
369 PerspectiveCanvas::PerspectiveCanvas(PerspectiveWindow *gui,
370         int x, int y, int w, int h)
371  : BC_SubWindow(x, y, w, h, BLACK)
372 {
373         this->gui = gui;
374         state = PerspectiveCanvas::NONE;
375 }
376
377
378 int PerspectiveCanvas::button_press_event()
379 {
380         if( is_event_win() && cursor_inside() ) {
381 // Set current point
382                 int cx = get_cursor_x(), cy = get_cursor_y();
383                 if( alt_down() && shift_down() ) {
384                         state = PerspectiveCanvas::DRAG_VIEW;
385                         start_x = cx;  start_y = cy;
386                         return 1;
387                 }
388
389                 PerspectiveMain *plugin = gui->plugin;
390                 int x1, y1, x2, y2, x3, y3, x4, y4;
391                 gui->calculate_canvas_coords(x1, y1, x2, y2, x3, y3, x4, y4);
392                 float cw = gui->canvas->get_w(), ch = gui->canvas->get_h();
393                 float x0 = cw / 2, y0 = ch / 2;
394                 float view_zoom = plugin->config.view_zoom;
395                 float view_x = plugin->config.view_x, view_y = plugin->config.view_y;
396                 int x = (cx-x0 - view_x) / view_zoom + x0;
397                 int y = (cy-y0 - view_y) / view_zoom + y0;
398                 float distance1 = DISTANCE(x, y, x1, y1);
399                 float distance2 = DISTANCE(x, y, x2, y2);
400                 float distance3 = DISTANCE(x, y, x3, y3);
401                 float distance4 = DISTANCE(x, y, x4, y4);
402                 float min = distance1;
403                 plugin->config.current_point = 0;
404                 if( distance2 < min ) {
405                         min = distance2;
406                         plugin->config.current_point = 1;
407                 }
408                 if( distance3 < min ) {
409                         min = distance3;
410                         plugin->config.current_point = 2;
411                 }
412                 if( distance4 < min ) {
413                         min = distance4;
414                         plugin->config.current_point = 3;
415                 }
416
417                 if( plugin->config.mode == AffineEngine::SHEER ) {
418                         if( plugin->config.current_point == 1 )
419                                 plugin->config.current_point = 0;
420                         else if( plugin->config.current_point == 2 )
421                                 plugin->config.current_point = 3;
422                 }
423                 start_x = x;
424                 start_y = y;
425                 if( alt_down() || shift_down() ) {
426                         state =  alt_down() ?
427                                 PerspectiveCanvas::DRAG_FULL :
428                                 PerspectiveCanvas::ZOOM;
429 // Get starting positions
430                         start_x1 = plugin->config.x1;
431                         start_y1 = plugin->config.y1;
432                         start_x2 = plugin->config.x2;
433                         start_y2 = plugin->config.y2;
434                         start_x3 = plugin->config.x3;
435                         start_y3 = plugin->config.y3;
436                         start_x4 = plugin->config.x4;
437                         start_y4 = plugin->config.y4;
438                 }
439                 else {
440                         state = PerspectiveCanvas::DRAG;
441 // Get starting positions
442                         start_x1 = plugin->get_current_x();
443                         start_y1 = plugin->get_current_y();
444                 }
445                 gui->update_coord();
446                 gui->update_canvas();
447                 return 1;
448         }
449
450         return 0;
451 }
452
453 int PerspectiveCanvas::button_release_event()
454 {
455         if( state == PerspectiveCanvas::NONE ) return 0;
456         state = PerspectiveCanvas::NONE;
457         return 1;
458 }
459
460 int PerspectiveCanvas::cursor_motion_event()
461 {
462         if( state == PerspectiveCanvas::NONE ) return 0;
463         PerspectiveMain *plugin = gui->plugin;
464         int cx = get_cursor_x(), cy = get_cursor_y();
465         if( state != PerspectiveCanvas::DRAG_VIEW ) {
466                 float cw = gui->canvas->get_w(), ch = gui->canvas->get_h();
467                 float x0 = cw / 2, y0 = ch / 2;
468                 float view_zoom = plugin->config.view_zoom;
469                 float view_x = plugin->config.view_x, view_y = plugin->config.view_y;
470                 int x = (cx-x0 - view_x) / view_zoom + x0;
471                 int y = (cy-y0 - view_y) / view_zoom + y0;
472                 int w1 = get_w() - 1, h1 = get_h() - 1;
473
474                 switch( state ) {
475                 case PerspectiveCanvas::DRAG:
476                         plugin->set_current_x((float)(x - start_x) / w1 * 100 + start_x1);
477                         plugin->set_current_y((float)(y - start_y) / h1 * 100 + start_y1);
478                         break;
479                 case PerspectiveCanvas::DRAG_FULL:
480                         plugin->config.x1 = ((float)(x - start_x) / w1 * 100 + start_x1);
481                         plugin->config.y1 = ((float)(y - start_y) / h1 * 100 + start_y1);
482                         plugin->config.x2 = ((float)(x - start_x) / w1 * 100 + start_x2);
483                         plugin->config.y2 = ((float)(y - start_y) / h1 * 100 + start_y2);
484                         plugin->config.x3 = ((float)(x - start_x) / w1 * 100 + start_x3);
485                         plugin->config.y3 = ((float)(y - start_y) / h1 * 100 + start_y3);
486                         plugin->config.x4 = ((float)(x - start_x) / w1 * 100 + start_x4);
487                         plugin->config.y4 = ((float)(y - start_y) / h1 * 100 + start_y4);
488                         break;
489                 case PerspectiveCanvas::ZOOM:
490                         float center_x = (start_x1 + start_x2 + start_x3 + start_x4) / 4;
491                         float center_y = (start_y1 + start_y2 + start_y3 + start_y4) / 4;
492                         float zoom = (float)(get_cursor_y() - start_y + 640) / 640;
493                         plugin->config.x1 = center_x + (start_x1 - center_x) * zoom;
494                         plugin->config.y1 = center_y + (start_y1 - center_y) * zoom;
495                         plugin->config.x2 = center_x + (start_x2 - center_x) * zoom;
496                         plugin->config.y2 = center_y + (start_y2 - center_y) * zoom;
497                         plugin->config.x3 = center_x + (start_x3 - center_x) * zoom;
498                         plugin->config.y3 = center_y + (start_y3 - center_y) * zoom;
499                         plugin->config.x4 = center_x + (start_x4 - center_x) * zoom;
500                         plugin->config.y4 = center_y + (start_y4 - center_y) * zoom;
501                         break;
502                 }
503                 gui->update_coord();
504         }
505         else {
506                 plugin->config.view_x += cx - start_x;
507                 plugin->config.view_y += cy - start_y;
508                 start_x = cx;  start_y = cy;
509         }
510         gui->update_canvas();
511         plugin->send_configure_change();
512         return 1;
513 }
514
515
516 PerspectiveCoord::PerspectiveCoord(PerspectiveWindow *gui,
517         int x, int y, float value, int is_x)
518  : BC_TumbleTextBox(gui, value, (float)-100, (float)200, x, y, 100)
519 {
520         this->gui = gui;
521         this->is_x = is_x;
522 }
523
524 int PerspectiveCoord::handle_event()
525 {
526         PerspectiveMain *plugin = gui->plugin;
527         float v = atof(get_text());
528         if( is_x )
529                 plugin->set_current_x(v);
530         else
531                 plugin->set_current_y(v);
532         gui->update_canvas();
533         plugin->send_configure_change();
534         return 1;
535 }
536
537
538 PerspectiveReset::PerspectiveReset(PerspectiveWindow *gui, int x, int y)
539  : BC_GenericButton(x, y, _("Reset"))
540 {
541         this->gui = gui;
542 }
543 int PerspectiveReset::handle_event()
544 {
545         gui->reset_view();
546
547         PerspectiveMain *plugin = gui->plugin;
548         plugin->config.x1 = 0;    plugin->config.y1 = 0;
549         plugin->config.x2 = 100;  plugin->config.y2 = 0;
550         plugin->config.x3 = 100;  plugin->config.y3 = 100;
551         plugin->config.x4 = 0;    plugin->config.y4 = 100;
552         gui->update_canvas();
553         gui->update_coord();
554         plugin->send_configure_change();
555         return 1;
556 }
557
558
559 PerspectiveMode::PerspectiveMode(PerspectiveWindow *gui,
560         int x, int y, int value, char *text)
561  : BC_Radial(x, y, gui->plugin->config.mode == value, text)
562 {
563         this->gui = gui;
564         this->value = value;
565 }
566 int PerspectiveMode::handle_event()
567 {
568         PerspectiveMain *plugin = gui->plugin;
569         plugin->config.mode = value;
570         gui->update_mode();
571         gui->update_canvas();
572         plugin->send_configure_change();
573         return 1;
574 }
575
576
577 PerspectiveDirection::PerspectiveDirection(PerspectiveWindow *gui,
578         int x, int y, int value, char *text)
579  : BC_Radial(x, y, gui->plugin->config.forward == value, text)
580 {
581         this->gui = gui;
582         this->value = value;
583 }
584 int PerspectiveDirection::handle_event()
585 {
586         PerspectiveMain *plugin = gui->plugin;
587         plugin->config.forward = value;
588         gui->update_mode();
589         plugin->send_configure_change();
590         return 1;
591 }
592
593
594 int PerspectiveAffineItem::handle_event()
595 {
596         ((PerspectiveAffine *)get_popup_menu())->update(id);
597         return 1;
598 }
599
600 PerspectiveAffine::PerspectiveAffine(PerspectiveWindow *gui, int x, int y)
601  : BC_PopupMenu(x, y, 100, "", 1)
602 {
603         this->gui = gui;
604         affine_modes[AffineEngine::AF_DEFAULT]     = _("default");
605         affine_modes[AffineEngine::AF_NEAREST]     = _("Nearest");
606         affine_modes[AffineEngine::AF_LINEAR]      = _("Linear");
607         affine_modes[AffineEngine::AF_CUBIC]       = _("Cubic");
608         mode = -1;
609 }
610 PerspectiveAffine::~PerspectiveAffine()
611 {
612         int id = total_items();
613         while( --id >= 0 )
614                 remove_item(get_item(id));
615         for( int id=0; id<n_modes; ++id )
616                 delete affine_items[id];
617 }
618 void PerspectiveAffine::affine_item(int id)
619 {
620         affine_items[id] = new PerspectiveAffineItem(affine_modes[id], id);
621         add_item(affine_items[id]);
622 }
623
624 void PerspectiveAffine::create_objects()
625 {
626         affine_item(AffineEngine::AF_DEFAULT);
627         affine_item(AffineEngine::AF_NEAREST);
628         affine_item(AffineEngine::AF_LINEAR);
629         affine_item(AffineEngine::AF_CUBIC);
630         PerspectiveMain *plugin = gui->plugin;
631         update(plugin->config.smoothing, 0);
632 }
633
634 void PerspectiveAffine::update(int mode, int send)
635 {
636         if( this->mode == mode ) return;
637         this->mode = mode;
638         set_text(affine_modes[mode]);
639         PerspectiveMain *plugin = gui->plugin;
640         plugin->config.smoothing = mode;
641         if( send ) plugin->send_configure_change();
642 }
643
644 PerspectiveMain::PerspectiveMain(PluginServer *server)
645  : PluginVClient(server)
646 {
647
648         engine = 0;
649         temp = 0;
650 }
651
652 PerspectiveMain::~PerspectiveMain()
653 {
654
655         if(engine) delete engine;
656         if(temp) delete temp;
657 }
658
659 const char* PerspectiveMain::plugin_title() { return N_("Perspective"); }
660 int PerspectiveMain::is_realtime() { return 1; }
661
662
663 NEW_WINDOW_MACRO(PerspectiveMain, PerspectiveWindow)
664
665 LOAD_CONFIGURATION_MACRO(PerspectiveMain, PerspectiveConfig)
666
667 void PerspectiveMain::update_gui()
668 {
669         if( !thread ) return;
670 //printf("PerspectiveMain::update_gui 1\n");
671         thread->window->lock_window();
672         PerspectiveWindow *gui = (PerspectiveWindow*)thread->window;
673 //printf("PerspectiveMain::update_gui 2\n");
674         load_configuration();
675         gui->update_coord();
676         gui->update_mode();
677         gui->update_view_zoom();
678         gui->update_canvas();
679         thread->window->unlock_window();
680 //printf("PerspectiveMain::update_gui 3\n");
681 }
682
683
684
685
686
687 void PerspectiveMain::save_data(KeyFrame *keyframe)
688 {
689         FileXML output;
690
691 // cause data to be stored directly in text
692         output.set_shared_output(keyframe->xbuf);
693         output.tag.set_title("PERSPECTIVE");
694
695         output.tag.set_property("X1", config.x1);
696         output.tag.set_property("X2", config.x2);
697         output.tag.set_property("X3", config.x3);
698         output.tag.set_property("X4", config.x4);
699         output.tag.set_property("Y1", config.y1);
700         output.tag.set_property("Y2", config.y2);
701         output.tag.set_property("Y3", config.y3);
702         output.tag.set_property("Y4", config.y4);
703
704         output.tag.set_property("MODE", config.mode);
705         output.tag.set_property("VIEW_X", config.view_x);
706         output.tag.set_property("VIEW_Y", config.view_y);
707         output.tag.set_property("VIEW_ZOOM", config.view_zoom);
708         output.tag.set_property("SMOOTHING", config.smoothing);
709         output.tag.set_property("FORWARD", config.forward);
710         output.tag.set_property("WINDOW_W", config.window_w);
711         output.tag.set_property("WINDOW_H", config.window_h);
712         output.append_tag();
713         output.tag.set_title("/PERSPECTIVE");
714         output.append_tag();
715         output.append_newline();
716         output.terminate_string();
717 }
718
719 void PerspectiveMain::read_data(KeyFrame *keyframe)
720 {
721         FileXML input;
722         input.set_shared_input(keyframe->xbuf);
723         int result = 0;
724
725         while(!(result = input.read_tag()) ) {
726                 if(input.tag.title_is("PERSPECTIVE")) {
727                         config.x1 = input.tag.get_property("X1", config.x1);
728                         config.x2 = input.tag.get_property("X2", config.x2);
729                         config.x3 = input.tag.get_property("X3", config.x3);
730                         config.x4 = input.tag.get_property("X4", config.x4);
731                         config.y1 = input.tag.get_property("Y1", config.y1);
732                         config.y2 = input.tag.get_property("Y2", config.y2);
733                         config.y3 = input.tag.get_property("Y3", config.y3);
734                         config.y4 = input.tag.get_property("Y4", config.y4);
735
736                         config.mode = input.tag.get_property("MODE", config.mode);
737                         config.view_x = input.tag.get_property("VIEW_X", config.view_x);
738                         config.view_y = input.tag.get_property("VIEW_Y", config.view_y);
739                         config.view_zoom = input.tag.get_property("VIEW_ZOOM", config.view_zoom);
740                         config.smoothing = input.tag.get_property("SMOOTHING", config.smoothing);
741                         config.forward = input.tag.get_property("FORWARD", config.forward);
742                         config.window_w = input.tag.get_property("WINDOW_W", config.window_w);
743                         config.window_h = input.tag.get_property("WINDOW_H", config.window_h);
744                 }
745         }
746 }
747
748 float PerspectiveMain::get_current_x()
749 {
750         switch( config.current_point ) {
751         case 0: return config.x1;
752         case 1: return config.x2;
753         case 2: return config.x3;
754         case 3: return config.x4;
755         }
756         return 0;
757 }
758
759 float PerspectiveMain::get_current_y()
760 {
761         switch( config.current_point ) {
762         case 0: return config.y1;
763         case 1: return config.y2;
764         case 2: return config.y3;
765         case 3: return config.y4;
766         }
767         return 0;
768 }
769
770 void PerspectiveMain::set_current_x(float value)
771 {
772         switch( config.current_point ) {
773         case 0: config.x1 = value; break;
774         case 1: config.x2 = value; break;
775         case 2: config.x3 = value; break;
776         case 3: config.x4 = value; break;
777         }
778 }
779
780 void PerspectiveMain::set_current_y(float value)
781 {
782         switch( config.current_point ) {
783         case 0: config.y1 = value; break;
784         case 1: config.y2 = value; break;
785         case 2: config.y3 = value; break;
786         case 3: config.y4 = value; break;
787         }
788 }
789
790 int PerspectiveMain::process_buffer(VFrame *frame,
791         int64_t start_position, double frame_rate)
792 {
793         /*int need_reconfigure =*/ load_configuration();
794         int smoothing = config.smoothing;
795 // default smoothing uses opengl if possible
796         int use_opengl = smoothing != AffineEngine::AF_DEFAULT ? 0 :
797 // Opengl does some funny business with stretching.
798                 config.mode == AffineEngine::PERSPECTIVE ||
799                 config.mode == AffineEngine::SHEER ? get_use_opengl() : 0;
800
801         read_frame(frame, 0, start_position, frame_rate, use_opengl);
802
803 // Do nothing
804
805         if( EQUIV(config.x1, 0)   && EQUIV(config.y1, 0) &&
806             EQUIV(config.x2, 100) && EQUIV(config.y2, 0) &&
807             EQUIV(config.x3, 100) && EQUIV(config.y3, 100) &&
808             EQUIV(config.x4, 0)   && EQUIV(config.y4, 100) )
809                 return 1;
810
811         if( !engine ) {
812                 int cpus = get_project_smp() + 1;
813                 engine = new AffineEngine(cpus, cpus);
814         }
815         engine->set_interpolation(smoothing);
816
817         if( use_opengl )
818                 return run_opengl();
819
820         this->input = frame;
821         this->output = frame;
822
823         int w = frame->get_w(), need_w = w;
824         int h = frame->get_h(), need_h = h;
825         int color_model = frame->get_color_model();
826         switch( config.mode ) {
827         case AffineEngine::STRETCH:
828                 need_w *= AFFINE_OVERSAMPLE;
829                 need_h *= AFFINE_OVERSAMPLE;
830         case AffineEngine::SHEER:
831         case AffineEngine::PERSPECTIVE:
832                 if( temp ) {
833                         if( temp->get_w() != need_w || temp->get_h() != need_h ||
834                             temp->get_color_model() != color_model ) {
835                                 delete temp;  temp = 0;
836                         }
837                 }
838                 if( !temp )
839                         temp = new VFrame(need_w, need_h, color_model, 0);
840                 break;
841         }
842         switch( config.mode ) {
843         case AffineEngine::STRETCH:
844                 temp->clear_frame();
845                 break;
846         case AffineEngine::PERSPECTIVE:
847         case AffineEngine::SHEER:
848                 temp->copy_from(input);
849                 input = temp;
850                 output->clear_frame();
851                 break;
852         default:
853                 delete temp;  temp = 0;
854                 break;
855         }
856
857         engine->process(output, input, temp, config.mode,
858                 config.x1, config.y1, config.x2, config.y2,
859                 config.x3, config.y3, config.x4, config.y4,
860                 config.forward);
861
862 // Resample
863
864         if( config.mode == AffineEngine::STRETCH ) {
865
866 #define RESAMPLE(tag, type, components, chroma_offset) \
867 case tag: { \
868     int os = AFFINE_OVERSAMPLE, os2 = os*os; \
869     for( int i=0; i<h; ++i ) { \
870         type *out_row = (type*)output->get_rows()[i]; \
871         type *in_row1 = (type*)temp->get_rows()[i * os]; \
872         type *in_row2 = (type*)temp->get_rows()[i * os + 1]; \
873         for( int j=0; j<w; ++j ) { \
874             out_row[0] = \
875                 ( in_row1[0] + in_row1[components + 0] +  \
876                   in_row2[0] + in_row2[components + 0] ) / os2; \
877             out_row[1] = \
878                 ( in_row1[1] + in_row1[components + 1] + \
879                   in_row2[1] + in_row2[components + 1] ) / os2; \
880             out_row[2] = \
881                 ( in_row1[2] + in_row1[components + 2] + \
882                   in_row2[2] + in_row2[components + 2] ) / os2; \
883             if( components == 4 ) { \
884                 out_row[3] = \
885                     ( in_row1[3] + in_row1[components + 3] +  \
886                       in_row2[3] + in_row2[components + 3] ) / os2; \
887             } \
888             out_row += components; \
889             in_row1 += components * os; \
890             in_row2 += components * os; \
891         } \
892     } \
893 } break
894
895                 switch( frame->get_color_model() ) {
896                 RESAMPLE( BC_RGB_FLOAT, float, 3, 0 );
897                 RESAMPLE( BC_RGB888, unsigned char, 3, 0 );
898                 RESAMPLE( BC_RGBA_FLOAT, float, 4, 0 );
899                 RESAMPLE( BC_RGBA8888, unsigned char, 4, 0 );
900                 RESAMPLE( BC_YUV888, unsigned char, 3, 0x80 );
901                 RESAMPLE( BC_YUVA8888, unsigned char, 4, 0x80 );
902                 RESAMPLE( BC_RGB161616, uint16_t, 3, 0 );
903                 RESAMPLE( BC_RGBA16161616, uint16_t, 4, 0 );
904                 RESAMPLE( BC_YUV161616, uint16_t, 3, 0x8000 );
905                 RESAMPLE( BC_YUVA16161616, uint16_t, 4, 0x8000 );
906                 }
907         }
908
909         return 0;
910 }
911
912
913 int PerspectiveMain::handle_opengl()
914 {
915 #ifdef HAVE_GL
916         engine->set_opengl(1);
917         engine->process(get_output(), get_output(), get_output(), config.mode,
918                 config.x1, config.y1, config.x2, config.y2,
919                 config.x3, config.y3, config.x4, config.y4,
920                 config.forward);
921         engine->set_opengl(0);
922 #endif
923         return 0;
924 }
925