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