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