add mask smooth boundary
[goodguy/cinelerra.git] / cinelerra-5.1 / plugins / motion-hv / motion-hv.C
1
2 /*
3  * CINELERRA
4  * Copyright (C) 2016 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 "bcdisplayinfo.h"
24 #include "clip.h"
25 #include "bchash.h"
26 #include "bcsignals.h"
27 #include "filexml.h"
28 #include "keyframe.h"
29 #include "language.h"
30 #include "motion-hv.h"
31 #include "motionscan-hv.h"
32 #include "motionwindow-hv.h"
33 #include "mutex.h"
34 #include "overlayframe.h"
35 #include "rotateframe.h"
36 #include "transportque.h"
37
38
39 #include <errno.h>
40 #include <unistd.h>
41
42 REGISTER_PLUGIN(MotionHVMain)
43
44 #undef DEBUG
45
46 // #ifndef DEBUG
47 // #define DEBUG
48 // #endif
49
50
51
52 MotionHVConfig::MotionHVConfig()
53 {
54         global_range_w = 5;
55         global_range_h = 5;
56         rotation_range = 5;
57         rotation_center = 0;
58         block_count = 1;
59         global_block_w = MIN_BLOCK;
60         global_block_h = MIN_BLOCK;
61 //      rotation_block_w = MIN_BLOCK;
62 //      rotation_block_h = MIN_BLOCK;
63         block_x = 50;
64         block_y = 50;
65 //      global_positions = 256;
66 //      rotate_positions = 4;
67         magnitude = 100;
68         rotate_magnitude = 90;
69         return_speed = 0;
70         rotate_return_speed = 0;
71         action_type = MotionHVScan::STABILIZE;
72 //      global = 1;
73         rotate = 1;
74         tracking_type = MotionHVScan::NO_CALCULATE;
75         draw_vectors = 1;
76         tracking_object = MotionHVScan::TRACK_SINGLE;
77         track_frame = 0;
78         bottom_is_master = 1;
79         horizontal_only = 0;
80         vertical_only = 0;
81 }
82
83 void MotionHVConfig::boundaries()
84 {
85         CLAMP(global_range_w, MIN_RADIUS, MAX_RADIUS);
86         CLAMP(global_range_h, MIN_RADIUS, MAX_RADIUS);
87         CLAMP(rotation_range, MIN_ROTATION, MAX_ROTATION);
88         CLAMP(rotation_center, -MAX_ROTATION, MAX_ROTATION);
89         CLAMP(block_count, MIN_BLOCKS, MAX_BLOCKS);
90         CLAMP(global_block_w, MIN_BLOCK, MAX_BLOCK);
91         CLAMP(global_block_h, MIN_BLOCK, MAX_BLOCK);
92 //      CLAMP(rotation_block_w, MIN_BLOCK, MAX_BLOCK);
93 //      CLAMP(rotation_block_h, MIN_BLOCK, MAX_BLOCK);
94 }
95
96 int MotionHVConfig::equivalent(MotionHVConfig &that)
97 {
98         return global_range_w == that.global_range_w &&
99                 global_range_h == that.global_range_h &&
100                 rotation_range == that.rotation_range &&
101                 rotation_center == that.rotation_center &&
102                 action_type == that.action_type &&
103 //              global == that.global &&
104                 rotate == that.rotate &&
105                 draw_vectors == that.draw_vectors &&
106                 block_count == that.block_count &&
107                 global_block_w == that.global_block_w &&
108                 global_block_h == that.global_block_h &&
109 //              rotation_block_w == that.rotation_block_w &&
110 //              rotation_block_h == that.rotation_block_h &&
111                 EQUIV(block_x, that.block_x) &&
112                 EQUIV(block_y, that.block_y) &&
113 //              global_positions == that.global_positions &&
114 //              rotate_positions == that.rotate_positions &&
115                 magnitude == that.magnitude &&
116                 return_speed == that.return_speed &&
117                 rotate_return_speed == that.rotate_return_speed &&
118                 rotate_magnitude == that.rotate_magnitude &&
119                 tracking_object == that.tracking_object &&
120                 track_frame == that.track_frame &&
121                 bottom_is_master == that.bottom_is_master &&
122                 horizontal_only == that.horizontal_only &&
123                 vertical_only == that.vertical_only &&
124                 tracking_type == that.tracking_type;
125 }
126
127 void MotionHVConfig::copy_from(MotionHVConfig &that)
128 {
129         global_range_w = that.global_range_w;
130         global_range_h = that.global_range_h;
131         rotation_range = that.rotation_range;
132         rotation_center = that.rotation_center;
133         action_type = that.action_type;
134 //      global = that.global;
135         rotate = that.rotate;
136         tracking_type = that.tracking_type;
137         draw_vectors = that.draw_vectors;
138         block_count = that.block_count;
139         block_x = that.block_x;
140         block_y = that.block_y;
141 //      global_positions = that.global_positions;
142 //      rotate_positions = that.rotate_positions;
143         global_block_w = that.global_block_w;
144         global_block_h = that.global_block_h;
145 //      rotation_block_w = that.rotation_block_w;
146 //      rotation_block_h = that.rotation_block_h;
147         magnitude = that.magnitude;
148         return_speed = that.return_speed;
149         rotate_magnitude = that.rotate_magnitude;
150         rotate_return_speed = that.rotate_return_speed;
151         tracking_object = that.tracking_object;
152         track_frame = that.track_frame;
153         bottom_is_master = that.bottom_is_master;
154         horizontal_only = that.horizontal_only;
155         vertical_only = that.vertical_only;
156 }
157
158 void MotionHVConfig::interpolate(MotionHVConfig &prev,
159         MotionHVConfig &next,
160         int64_t prev_frame,
161         int64_t next_frame,
162         int64_t current_frame)
163 {
164         //double next_scale = (double)(current_frame - prev_frame) / (next_frame - prev_frame);
165         //double prev_scale = (double)(next_frame - current_frame) / (next_frame - prev_frame);
166         this->block_x = prev.block_x;
167         this->block_y = prev.block_y;
168         global_range_w = prev.global_range_w;
169         global_range_h = prev.global_range_h;
170         rotation_range = prev.rotation_range;
171         rotation_center = prev.rotation_center;
172         action_type = prev.action_type;
173 //      global = prev.global;
174         rotate = prev.rotate;
175         tracking_type = prev.tracking_type;
176         draw_vectors = prev.draw_vectors;
177         block_count = prev.block_count;
178 //      global_positions = prev.global_positions;
179 //      rotate_positions = prev.rotate_positions;
180         global_block_w = prev.global_block_w;
181         global_block_h = prev.global_block_h;
182 //      rotation_block_w = prev.rotation_block_w;
183 //      rotation_block_h = prev.rotation_block_h;
184         magnitude = prev.magnitude;
185         return_speed = prev.return_speed;
186         rotate_magnitude = prev.rotate_magnitude;
187         rotate_return_speed = prev.rotate_return_speed;
188         tracking_object = prev.tracking_object;
189         track_frame = prev.track_frame;
190         bottom_is_master = prev.bottom_is_master;
191         horizontal_only = prev.horizontal_only;
192         vertical_only = prev.vertical_only;
193 }
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213 MotionHVMain::MotionHVMain(PluginServer *server)
214  : PluginVClient(server)
215 {
216         engine = 0;
217         rotate_engine = 0;
218 //      motion_rotate = 0;
219         total_dx = 0;
220         total_dy = 0;
221         total_angle = 0;
222         overlayer = 0;
223         search_area = 0;
224         search_size = 0;
225         temp_frame = 0;
226         previous_frame_number = -1;
227
228         prev_global_ref = 0;
229         current_global_ref = 0;
230         global_target_src = 0;
231         global_target_dst = 0;
232
233         prev_rotate_ref = 0;
234         current_rotate_ref = 0;
235         rotate_target_src = 0;
236         rotate_target_dst = 0;
237 }
238
239 MotionHVMain::~MotionHVMain()
240 {
241
242         delete engine;
243         delete overlayer;
244         delete [] search_area;
245         delete temp_frame;
246         delete rotate_engine;
247 //      delete motion_rotate;
248
249
250         delete prev_global_ref;
251         delete current_global_ref;
252         delete global_target_src;
253         delete global_target_dst;
254
255         delete prev_rotate_ref;
256         delete current_rotate_ref;
257         delete rotate_target_src;
258         delete rotate_target_dst;
259 }
260
261 const char* MotionHVMain::plugin_title() { return N_("MotionHV"); }
262 int MotionHVMain::is_realtime() { return 1; }
263 int MotionHVMain::is_multichannel() { return 1; }
264
265
266 NEW_WINDOW_MACRO(MotionHVMain, MotionHVWindow)
267
268 LOAD_CONFIGURATION_MACRO(MotionHVMain, MotionHVConfig)
269
270
271
272 void MotionHVMain::update_gui()
273 {
274         if(thread)
275         {
276                 if(load_configuration())
277                 {
278                         thread->window->lock_window("MotionHVMain::update_gui");
279
280 //                      char string[BCTEXTLEN];
281 //                      sprintf(string, "%d", config.global_positions);
282 //                      ((MotionHVWindow*)thread->window)->global_search_positions->set_text(string);
283 //                      sprintf(string, "%d", config.rotate_positions);
284 //                      ((MotionHVWindow*)thread->window)->rotation_search_positions->set_text(string);
285
286                         ((MotionHVWindow*)thread->window)->global_block_w->update(config.global_block_w);
287                         ((MotionHVWindow*)thread->window)->global_block_h->update(config.global_block_h);
288 //                      ((MotionHVWindow*)thread->window)->rotation_block_w->update(config.rotation_block_w);
289 //                      ((MotionHVWindow*)thread->window)->rotation_block_h->update(config.rotation_block_h);
290                         ((MotionHVWindow*)thread->window)->block_x->update(config.block_x);
291                         ((MotionHVWindow*)thread->window)->block_y->update(config.block_y);
292                         ((MotionHVWindow*)thread->window)->block_x_text->update((float)config.block_x);
293                         ((MotionHVWindow*)thread->window)->block_y_text->update((float)config.block_y);
294                         ((MotionHVWindow*)thread->window)->magnitude->update(config.magnitude);
295                         ((MotionHVWindow*)thread->window)->return_speed->update(config.return_speed);
296                         ((MotionHVWindow*)thread->window)->rotate_magnitude->update(config.rotate_magnitude);
297                         ((MotionHVWindow*)thread->window)->rotate_return_speed->update(config.rotate_return_speed);
298                         ((MotionHVWindow*)thread->window)->rotation_range->update(config.rotation_range);
299                         ((MotionHVWindow*)thread->window)->rotation_center->update(config.rotation_center);
300
301
302                         ((MotionHVWindow*)thread->window)->track_single->update(config.tracking_object == MotionHVScan::TRACK_SINGLE);
303                         ((MotionHVWindow*)thread->window)->track_frame_number->update(config.track_frame);
304                         ((MotionHVWindow*)thread->window)->track_previous->update(config.tracking_object == MotionHVScan::TRACK_PREVIOUS);
305                         ((MotionHVWindow*)thread->window)->previous_same->update(config.tracking_object == MotionHVScan::PREVIOUS_SAME_BLOCK);
306                         if(config.tracking_object != MotionHVScan::TRACK_SINGLE)
307                                 ((MotionHVWindow*)thread->window)->track_frame_number->disable();
308                         else
309                                 ((MotionHVWindow*)thread->window)->track_frame_number->enable();
310
311                         ((MotionHVWindow*)thread->window)->action_type->set_text(
312                                 ActionType::to_text(config.action_type));
313                         ((MotionHVWindow*)thread->window)->tracking_type->set_text(
314                                 TrackingType::to_text(config.tracking_type));
315                         ((MotionHVWindow*)thread->window)->track_direction->set_text(
316                                 TrackDirection::to_text(config.horizontal_only, config.vertical_only));
317                         ((MotionHVWindow*)thread->window)->master_layer->set_text(
318                                 MasterLayer::to_text(config.bottom_is_master));
319
320
321                         ((MotionHVWindow*)thread->window)->update_mode();
322                         thread->window->unlock_window();
323                 }
324         }
325 }
326
327
328
329
330 void MotionHVMain::save_data(KeyFrame *keyframe)
331 {
332         FileXML output;
333
334 // cause data to be stored directly in text
335         output.set_shared_output(keyframe->xbuf);
336         output.tag.set_title("MOTIONHV");
337
338         output.tag.set_property("BLOCK_COUNT", config.block_count);
339 //      output.tag.set_property("GLOBAL_POSITIONS", config.global_positions);
340 //      output.tag.set_property("ROTATE_POSITIONS", config.rotate_positions);
341         output.tag.set_property("GLOBAL_BLOCK_W", config.global_block_w);
342         output.tag.set_property("GLOBAL_BLOCK_H", config.global_block_h);
343 //      output.tag.set_property("ROTATION_BLOCK_W", config.rotation_block_w);
344 //      output.tag.set_property("ROTATION_BLOCK_H", config.rotation_block_h);
345         output.tag.set_property("BLOCK_X", config.block_x);
346         output.tag.set_property("BLOCK_Y", config.block_y);
347         output.tag.set_property("GLOBAL_RANGE_W", config.global_range_w);
348         output.tag.set_property("GLOBAL_RANGE_H", config.global_range_h);
349         output.tag.set_property("ROTATION_RANGE", config.rotation_range);
350         output.tag.set_property("ROTATION_CENTER", config.rotation_center);
351         output.tag.set_property("MAGNITUDE", config.magnitude);
352         output.tag.set_property("RETURN_SPEED", config.return_speed);
353         output.tag.set_property("ROTATE_MAGNITUDE", config.rotate_magnitude);
354         output.tag.set_property("ROTATE_RETURN_SPEED", config.rotate_return_speed);
355         output.tag.set_property("ACTION_TYPE", config.action_type);
356 //      output.tag.set_property("GLOBAL", config.global);
357         output.tag.set_property("ROTATE", config.rotate);
358         output.tag.set_property("TRACKING_TYPE", config.tracking_type);
359         output.tag.set_property("DRAW_VECTORS", config.draw_vectors);
360         output.tag.set_property("TRACKING_OBJECT", config.tracking_object);
361         output.tag.set_property("TRACK_FRAME", config.track_frame);
362         output.tag.set_property("BOTTOM_IS_MASTER", config.bottom_is_master);
363         output.tag.set_property("HORIZONTAL_ONLY", config.horizontal_only);
364         output.tag.set_property("VERTICAL_ONLY", config.vertical_only);
365         output.append_tag();
366         output.tag.set_title("/MOTIONHV");
367         output.append_tag();
368         output.terminate_string();
369 }
370
371 void MotionHVMain::read_data(KeyFrame *keyframe)
372 {
373         FileXML input;
374
375         input.set_shared_input(keyframe->xbuf);
376
377         int result = 0;
378
379         while(!result)
380         {
381                 result = input.read_tag();
382
383                 if(!result)
384                 {
385                         if(input.tag.title_is("MOTIONHV"))
386                         {
387                                 config.block_count = input.tag.get_property("BLOCK_COUNT", config.block_count);
388 //                              config.global_positions = input.tag.get_property("GLOBAL_POSITIONS", config.global_positions);
389 //                              config.rotate_positions = input.tag.get_property("ROTATE_POSITIONS", config.rotate_positions);
390                                 config.global_block_w = input.tag.get_property("GLOBAL_BLOCK_W", config.global_block_w);
391                                 config.global_block_h = input.tag.get_property("GLOBAL_BLOCK_H", config.global_block_h);
392 //                              config.rotation_block_w = input.tag.get_property("ROTATION_BLOCK_W", config.rotation_block_w);
393 //                              config.rotation_block_h = input.tag.get_property("ROTATION_BLOCK_H", config.rotation_block_h);
394                                 config.block_x = input.tag.get_property("BLOCK_X", config.block_x);
395                                 config.block_y = input.tag.get_property("BLOCK_Y", config.block_y);
396                                 config.global_range_w = input.tag.get_property("GLOBAL_RANGE_W", config.global_range_w);
397                                 config.global_range_h = input.tag.get_property("GLOBAL_RANGE_H", config.global_range_h);
398                                 config.rotation_range = input.tag.get_property("ROTATION_RANGE", config.rotation_range);
399                                 config.rotation_center = input.tag.get_property("ROTATION_CENTER", config.rotation_center);
400                                 config.magnitude = input.tag.get_property("MAGNITUDE", config.magnitude);
401                                 config.return_speed = input.tag.get_property("RETURN_SPEED", config.return_speed);
402                                 config.rotate_magnitude = input.tag.get_property("ROTATE_MAGNITUDE", config.rotate_magnitude);
403                                 config.rotate_return_speed = input.tag.get_property("ROTATE_RETURN_SPEED", config.rotate_return_speed);
404                                 config.action_type = input.tag.get_property("ACTION_TYPE", config.action_type);
405 //                              config.global = input.tag.get_property("GLOBAL", config.global);
406                                 config.rotate = input.tag.get_property("ROTATE", config.rotate);
407                                 config.tracking_type = input.tag.get_property("TRACKING_TYPE", config.tracking_type);
408                                 config.draw_vectors = input.tag.get_property("DRAW_VECTORS", config.draw_vectors);
409                                 config.tracking_object = input.tag.get_property("TRACKING_OBJECT", config.tracking_object);
410                                 config.track_frame = input.tag.get_property("TRACK_FRAME", config.track_frame);
411                                 config.bottom_is_master = input.tag.get_property("BOTTOM_IS_MASTER", config.bottom_is_master);
412                                 config.horizontal_only = input.tag.get_property("HORIZONTAL_ONLY", config.horizontal_only);
413                                 config.vertical_only = input.tag.get_property("VERTICAL_ONLY", config.vertical_only);
414                         }
415                 }
416         }
417         config.boundaries();
418 }
419
420
421
422
423
424
425
426
427
428 void MotionHVMain::allocate_temp(int w, int h, int color_model)
429 {
430         if(temp_frame &&
431                 (temp_frame->get_w() != w ||
432                 temp_frame->get_h() != h))
433         {
434                 delete temp_frame;
435                 temp_frame = 0;
436         }
437         if(!temp_frame)
438                 temp_frame = new VFrame(w, h, color_model);
439 }
440
441
442 void MotionHVMain::process_global()
443 {
444         int w = current_global_ref->get_w();
445         int h = current_global_ref->get_h();
446
447
448         if(!engine) engine = new MotionHVScan(PluginClient::get_project_smp() + 1,
449                 PluginClient::get_project_smp() + 1);
450
451 // Determine if frames changed
452 // printf("MotionHVMain::process_global %d block_y=%f total_dy=%d\n",
453 //  __LINE__, config.block_y * h / 100, total_dy);
454         engine->scan_frame(current_global_ref,
455                 prev_global_ref,
456                 config.global_range_w * w / 100,
457                 config.global_range_h * h / 100,
458                 config.global_block_w * w / 100,
459                 config.global_block_h * h / 100,
460                 config.block_x * w / 100,
461                 config.block_y * h / 100,
462                 config.tracking_object,
463                 config.tracking_type,
464                 config.action_type,
465                 config.horizontal_only,
466                 config.vertical_only,
467                 get_source_position(),
468                 total_dx,
469                 total_dy,
470                 0,
471                 0,
472                 1, // do_motion
473                 config.rotate, // do_rotate
474                 config.rotation_center,
475                 config.rotation_range);
476
477         current_dx = engine->dx_result;
478         current_dy = engine->dy_result;
479
480 // Add current motion vector to accumulation vector.
481         if(config.tracking_object != MotionHVScan::TRACK_SINGLE)
482         {
483 // Retract over time
484                 total_dx = (int64_t)total_dx * (100 - config.return_speed) / 100;
485                 total_dy = (int64_t)total_dy * (100 - config.return_speed) / 100;
486                 total_dx += engine->dx_result;
487                 total_dy += engine->dy_result;
488 // printf("MotionHVMain::process_global %d total_dy=%d engine->dy_result=%d\n",
489 //  __LINE__, total_dy, engine->dy_result);
490         }
491         else
492 // Make accumulation vector current
493         {
494                 total_dx = engine->dx_result;
495                 total_dy = engine->dy_result;
496         }
497
498 // Clamp accumulation vector
499         if(config.magnitude < 100)
500         {
501                 //int block_w = (int64_t)config.global_block_w * w / 100;
502                 //int block_h = (int64_t)config.global_block_h * h / 100;
503                 int block_x_orig = (int64_t)(config.block_x * w / 100);
504                 int block_y_orig = (int64_t)(config.block_y *
505                         current_global_ref->get_h() / h / 100);
506
507                 int max_block_x = (int64_t)(w - block_x_orig) *
508                         OVERSAMPLE * config.magnitude / 100;
509                 int max_block_y = (int64_t)(h - block_y_orig) *
510                         OVERSAMPLE * config.magnitude / 100;
511                 int min_block_x = (int64_t)-block_x_orig *
512                         OVERSAMPLE * config.magnitude / 100;
513                 int min_block_y = (int64_t)-block_y_orig *
514                         OVERSAMPLE * config.magnitude / 100;
515
516                 CLAMP(total_dx, min_block_x, max_block_x);
517                 CLAMP(total_dy, min_block_y, max_block_y);
518         }
519
520 // printf("MotionHVMain::process_global %d total_dx=%d total_dy=%d\n",
521 //  __LINE__, total_dx, total_dy);
522
523         if(config.tracking_object != MotionHVScan::TRACK_SINGLE && !config.rotate)
524         {
525 // Transfer current reference frame to previous reference frame and update
526 // counter.  Must wait for rotate to compare.
527                 prev_global_ref->copy_from(current_global_ref);
528                 previous_frame_number = get_source_position();
529         }
530
531 // Decide what to do with target based on requested operation
532         int interpolation = NEAREST_NEIGHBOR;
533         float dx = 0.;
534         float dy = 0.;
535         switch(config.action_type)
536         {
537                 case MotionHVScan::NOTHING:
538                         global_target_dst->copy_from(global_target_src);
539                         break;
540                 case MotionHVScan::TRACK_PIXEL:
541                         interpolation = NEAREST_NEIGHBOR;
542                         dx = (int)(total_dx / OVERSAMPLE);
543                         dy = (int)(total_dy / OVERSAMPLE);
544                         break;
545                 case MotionHVScan::STABILIZE_PIXEL:
546                         interpolation = NEAREST_NEIGHBOR;
547                         dx = -(int)(total_dx / OVERSAMPLE);
548                         dy = -(int)(total_dy / OVERSAMPLE);
549                         break;
550                 case MotionHVScan::TRACK:
551                         interpolation = CUBIC_LINEAR;
552                         dx = (float)total_dx / OVERSAMPLE;
553                         dy = (float)total_dy / OVERSAMPLE;
554                         break;
555                 case MotionHVScan::STABILIZE:
556                         interpolation = CUBIC_LINEAR;
557                         dx = -(float)total_dx / OVERSAMPLE;
558                         dy = -(float)total_dy / OVERSAMPLE;
559                         break;
560         }
561
562
563         if(config.action_type != MotionHVScan::NOTHING)
564         {
565                 if(!overlayer)
566                         overlayer = new OverlayFrame(PluginClient::get_project_smp() + 1);
567                 global_target_dst->clear_frame();
568                 overlayer->overlay(global_target_dst,
569                         global_target_src,
570                         0,
571                         0,
572                         global_target_src->get_w(),
573                         global_target_src->get_h(),
574                         dx,
575                         dy,
576                         (float)global_target_src->get_w() + dx,
577                         (float)global_target_src->get_h() + dy,
578                         1,
579                         TRANSFER_REPLACE,
580                         interpolation);
581         }
582 }
583
584
585
586 void MotionHVMain::process_rotation()
587 {
588         int block_x;
589         int block_y;
590
591 // Always require global
592 // Convert the previous global reference into the previous rotation reference.
593 // Convert global target destination into rotation target source.
594 //      if(config.global)
595         if(1)
596         {
597                 if(!overlayer)
598                         overlayer = new OverlayFrame(PluginClient::get_project_smp() + 1);
599                 float dx;
600                 float dy;
601                 if(config.tracking_object == MotionHVScan::TRACK_SINGLE)
602                 {
603                         dx = (float)total_dx / OVERSAMPLE;
604                         dy = (float)total_dy / OVERSAMPLE;
605                 }
606                 else
607                 {
608                         dx = (float)current_dx / OVERSAMPLE;
609                         dy = (float)current_dy / OVERSAMPLE;
610                 }
611
612                 prev_rotate_ref->clear_frame();
613                 overlayer->overlay(prev_rotate_ref,
614                         prev_global_ref,
615                         0,
616                         0,
617                         prev_global_ref->get_w(),
618                         prev_global_ref->get_h(),
619                         dx,
620                         dy,
621                         (float)prev_global_ref->get_w() + dx,
622                         (float)prev_global_ref->get_h() + dy,
623                         1,
624                         TRANSFER_REPLACE,
625                         CUBIC_LINEAR);
626 // Pivot is destination global position
627                 block_x = (int)(prev_rotate_ref->get_w() *
628                         config.block_x /
629                         100 +
630                         (float)total_dx /
631                         OVERSAMPLE);
632                 block_y = (int)(prev_rotate_ref->get_h() *
633                         config.block_y /
634                         100 +
635                         (float)total_dy /
636                         OVERSAMPLE);
637 // Use the global target output as the rotation target input
638                 rotate_target_src->copy_from(global_target_dst);
639 // Transfer current reference frame to previous reference frame for global.
640                 if(config.tracking_object != MotionHVScan::TRACK_SINGLE)
641                 {
642                         prev_global_ref->copy_from(current_global_ref);
643                         previous_frame_number = get_source_position();
644                 }
645         }
646         else
647         {
648 // Pivot is fixed
649                 block_x = (int)(prev_rotate_ref->get_w() *
650                         config.block_x /
651                         100);
652                 block_y = (int)(prev_rotate_ref->get_h() *
653                         config.block_y /
654                         100);
655         }
656
657
658
659 // Get rotation
660 //      if(!motion_rotate)
661 //              motion_rotate = new RotateScan(this,
662 //                      get_project_smp() + 1,
663 //                      get_project_smp() + 1);
664 //
665 //      current_angle = motion_rotate->scan_frame(prev_rotate_ref,
666 //              current_rotate_ref,
667 //              block_x,
668 //              block_y);
669
670         current_angle = engine->dr_result;
671
672 // Add current rotation to accumulation
673         if(config.tracking_object != MotionHVScan::TRACK_SINGLE)
674         {
675 // Retract over time
676                 total_angle = total_angle * (100 - config.rotate_return_speed) / 100;
677 // Accumulate current rotation
678                 total_angle += current_angle;
679
680 // Clamp rotation accumulation
681                 if(config.rotate_magnitude < 90)
682                 {
683                         CLAMP(total_angle, -config.rotate_magnitude, config.rotate_magnitude);
684                 }
685
686 //              if(!config.global)
687 //              {
688 // Transfer current reference frame to previous reference frame and update
689 // counter.
690 //                      prev_rotate_ref->copy_from(current_rotate_ref);
691 //                      previous_frame_number = get_source_position();
692 //              }
693         }
694         else
695         {
696                 total_angle = current_angle;
697         }
698
699 #ifdef DEBUG
700 printf("MotionHVMain::process_rotation total_angle=%f\n", total_angle);
701 #endif
702
703
704 // Calculate rotation parameters based on requested operation
705         float angle = 0.;
706         switch(config.action_type)
707         {
708                 case MotionHVScan::NOTHING:
709                         rotate_target_dst->copy_from(rotate_target_src);
710                         break;
711                 case MotionHVScan::TRACK:
712                 case MotionHVScan::TRACK_PIXEL:
713                         angle = total_angle;
714                         break;
715                 case MotionHVScan::STABILIZE:
716                 case MotionHVScan::STABILIZE_PIXEL:
717                         angle = -total_angle;
718                         break;
719         }
720
721
722
723         if(config.action_type != MotionHVScan::NOTHING)
724         {
725                 if(!rotate_engine)
726                         rotate_engine = new AffineEngine(PluginClient::get_project_smp() + 1,
727                                 PluginClient::get_project_smp() + 1);
728
729                 rotate_target_dst->clear_frame();
730
731 // Determine pivot based on a number of factors.
732                 switch(config.action_type)
733                 {
734                         case MotionHVScan::TRACK:
735                         case MotionHVScan::TRACK_PIXEL:
736 // Use destination of global tracking.
737 //                              rotate_engine->set_pivot(block_x, block_y);
738                                 rotate_engine->set_in_pivot(block_x, block_y);
739                                 rotate_engine->set_out_pivot(block_x, block_y);
740                                 break;
741
742                         case MotionHVScan::STABILIZE:
743                         case MotionHVScan::STABILIZE_PIXEL:
744 //                              if(config.global)
745                                 if(1)
746                                 {
747 // Use origin of global stabilize operation
748 //                                      rotate_engine->set_pivot((int)(rotate_target_dst->get_w() *
749 //                                                      config.block_x /
750 //                                                      100),
751 //                                              (int)(rotate_target_dst->get_h() *
752 //                                                      config.block_y /
753 //                                                      100));
754                                         rotate_engine->set_in_pivot((int)(rotate_target_dst->get_w() *
755                                                         config.block_x /
756                                                         100),
757                                                 (int)(rotate_target_dst->get_h() *
758                                                         config.block_y /
759                                                         100));
760                                         rotate_engine->set_out_pivot((int)(rotate_target_dst->get_w() *
761                                                         config.block_x /
762                                                         100),
763                                                 (int)(rotate_target_dst->get_h() *
764                                                         config.block_y /
765                                                         100));
766
767                                 }
768                                 else
769                                 {
770 // Use origin
771 //                                      rotate_engine->set_pivot(block_x, block_y);
772                                         rotate_engine->set_in_pivot(block_x, block_y);
773                                         rotate_engine->set_out_pivot(block_x, block_y);
774                                 }
775                                 break;
776                 }
777
778
779 printf("MotionHVMain::process_rotation angle=%f\n", angle);
780                 rotate_engine->rotate(rotate_target_dst, rotate_target_src, angle);
781 // overlayer->overlay(rotate_target_dst,
782 //      prev_rotate_ref,
783 //      0,
784 //      0,
785 //      prev_rotate_ref->get_w(),
786 //      prev_rotate_ref->get_h(),
787 //      0,
788 //      0,
789 //      prev_rotate_ref->get_w(),
790 //      prev_rotate_ref->get_h(),
791 //      1,
792 //      TRANSFER_NORMAL,
793 //      CUBIC_LINEAR);
794 // overlayer->overlay(rotate_target_dst,
795 //      current_rotate_ref,
796 //      0,
797 //      0,
798 //      prev_rotate_ref->get_w(),
799 //      prev_rotate_ref->get_h(),
800 //      0,
801 //      0,
802 //      prev_rotate_ref->get_w(),
803 //      prev_rotate_ref->get_h(),
804 //      1,
805 //      TRANSFER_NORMAL,
806 //      CUBIC_LINEAR);
807
808
809         }
810
811
812 }
813
814
815
816
817
818
819
820
821
822 int MotionHVMain::process_buffer(VFrame **frame,
823         int64_t start_position,
824         double frame_rate)
825 {
826         int need_reconfigure = load_configuration();
827         int color_model = frame[0]->get_color_model();
828         w = frame[0]->get_w();
829         h = frame[0]->get_h();
830
831
832 #ifdef DEBUG
833 printf("MotionHVMain::process_buffer %d start_position=%lld\n", __LINE__, start_position);
834 #endif
835
836
837 // Calculate the source and destination pointers for each of the operations.
838 // Get the layer to track motion in.
839         reference_layer = config.bottom_is_master ?
840                 PluginClient::total_in_buffers - 1 :
841                 0;
842 // Get the layer to apply motion in.
843         target_layer = config.bottom_is_master ?
844                 0 :
845                 PluginClient::total_in_buffers - 1;
846
847
848         output_frame = frame[target_layer];
849
850
851 // Get the position of previous reference frame.
852         int64_t actual_previous_number;
853 // Skip if match frame not available
854         int skip_current = 0;
855
856
857         if(config.tracking_object == MotionHVScan::TRACK_SINGLE)
858         {
859                 actual_previous_number = config.track_frame;
860                 if(get_direction() == PLAY_REVERSE)
861                         actual_previous_number++;
862                 if(actual_previous_number == start_position)
863                         skip_current = 1;
864         }
865         else
866         {
867                 actual_previous_number = start_position;
868                 if(get_direction() == PLAY_FORWARD)
869                 {
870                         actual_previous_number--;
871                         if(actual_previous_number < get_source_start())
872                                 skip_current = 1;
873                         else
874                         {
875                                 KeyFrame *keyframe = get_prev_keyframe(start_position, 1);
876                                 if(keyframe->position > 0 &&
877                                         actual_previous_number < keyframe->position)
878                                         skip_current = 1;
879                         }
880                 }
881                 else
882                 {
883                         actual_previous_number++;
884                         if(actual_previous_number >= get_source_start() + get_total_len())
885                                 skip_current = 1;
886                         else
887                         {
888                                 KeyFrame *keyframe = get_next_keyframe(start_position, 1);
889                                 if(keyframe->position > 0 &&
890                                         actual_previous_number >= keyframe->position)
891                                         skip_current = 1;
892                         }
893                 }
894
895 // Only count motion since last keyframe
896
897
898         }
899
900
901 //      if(!config.global && !config.rotate) skip_current = 1;
902
903
904
905
906 // printf("process_realtime %d %lld %lld\n",
907 // skip_current,
908 // previous_frame_number,
909 // actual_previous_number);
910 // Load match frame and reset vectors
911         int need_reload = !skip_current &&
912                 (previous_frame_number != actual_previous_number ||
913                 need_reconfigure);
914         if(need_reload)
915         {
916                 total_dx = 0;
917                 total_dy = 0;
918                 total_angle = 0;
919                 previous_frame_number = actual_previous_number;
920         }
921
922
923         if(skip_current)
924         {
925                 total_dx = 0;
926                 total_dy = 0;
927                 current_dx = 0;
928                 current_dy = 0;
929                 total_angle = 0;
930                 current_angle = 0;
931         }
932
933
934
935
936 // Get the global pointers.  Here we walk through the sequence of events.
937 //      if(config.global)
938         if(1)
939         {
940 // Assume global only.  Global reads previous frame and compares
941 // with current frame to get the current translation.
942 // The center of the search area is fixed in compensate mode or
943 // the user value + the accumulation vector in track mode.
944                 if(!prev_global_ref)
945                         prev_global_ref = new VFrame(w, h, color_model);
946                 if(!current_global_ref)
947                         current_global_ref = new VFrame(w, h, color_model);
948
949 // Global loads the current target frame into the src and
950 // writes it to the dst frame with desired translation.
951                 if(!global_target_src)
952                         global_target_src = new VFrame(w, h, color_model);
953                 if(!global_target_dst)
954                         global_target_dst = new VFrame(w, h, color_model);
955
956
957 // Load the global frames
958                 if(need_reload)
959                 {
960                         read_frame(prev_global_ref,
961                                 reference_layer,
962                                 previous_frame_number,
963                                 frame_rate,
964                                 0);
965                 }
966
967                 read_frame(current_global_ref,
968                         reference_layer,
969                         start_position,
970                         frame_rate,
971                         0);
972                 read_frame(global_target_src,
973                         target_layer,
974                         start_position,
975                         frame_rate,
976                         0);
977
978
979
980 // Global followed by rotate
981                 if(config.rotate)
982                 {
983 // Must translate the previous global reference by the current global
984 // accumulation vector to match the current global reference.
985 // The center of the search area is always the user value + the accumulation
986 // vector.
987                         if(!prev_rotate_ref)
988                                 prev_rotate_ref = new VFrame(w, h, color_model);
989 // The current global reference is the current rotation reference.
990                         if(!current_rotate_ref)
991                                 current_rotate_ref = new VFrame(w, h, color_model);
992                         current_rotate_ref->copy_from(current_global_ref);
993
994 // The global target destination is copied to the rotation target source
995 // then written to the rotation output with rotation.
996 // The pivot for the rotation is the center of the search area
997 // if we're tracking.
998 // The pivot is fixed to the user position if we're compensating.
999                         if(!rotate_target_src)
1000                                 rotate_target_src = new VFrame(w, h, color_model);
1001                         if(!rotate_target_dst)
1002                                 rotate_target_dst = new VFrame(w, h, color_model);
1003                 }
1004         }
1005         else
1006 // Rotation only
1007         if(config.rotate)
1008         {
1009 // Rotation reads the previous reference frame and compares it with current
1010 // reference frame.
1011                 if(!prev_rotate_ref)
1012                         prev_rotate_ref = new VFrame(w, h, color_model);
1013                 if(!current_rotate_ref)
1014                         current_rotate_ref = new VFrame(w, h, color_model);
1015
1016 // Rotation loads target frame to temporary, rotates it, and writes it to the
1017 // target frame.  The pivot is always fixed.
1018                 if(!rotate_target_src)
1019                         rotate_target_src = new VFrame(w, h, color_model);
1020                 if(!rotate_target_dst)
1021                         rotate_target_dst = new VFrame(w, h, color_model);
1022
1023
1024 // Load the rotate frames
1025                 if(need_reload)
1026                 {
1027                         read_frame(prev_rotate_ref,
1028                                 reference_layer,
1029                                 previous_frame_number,
1030                                 frame_rate,
1031                                 0);
1032                 }
1033                 read_frame(current_rotate_ref,
1034                         reference_layer,
1035                         start_position,
1036                         frame_rate,
1037                         0);
1038                 read_frame(rotate_target_src,
1039                         target_layer,
1040                         start_position,
1041                         frame_rate,
1042                         0);
1043         }
1044
1045
1046
1047
1048
1049
1050
1051 //PRINT_TRACE
1052 //printf("skip_current=%d config.global=%d\n", skip_current, config.global);
1053
1054
1055         if(!skip_current)
1056         {
1057 // Get position change from previous frame to current frame
1058                 /* if(config.global) */ process_global();
1059 // Get rotation change from previous frame to current frame
1060                 if(config.rotate) process_rotation();
1061 //frame[target_layer]->copy_from(prev_rotate_ref);
1062 //frame[target_layer]->copy_from(current_rotate_ref);
1063         }
1064
1065
1066
1067
1068
1069
1070 // Transfer the relevant target frame to the output
1071         if(!skip_current)
1072         {
1073                 if(config.rotate)
1074                 {
1075                         frame[target_layer]->copy_from(rotate_target_dst);
1076                 }
1077                 else
1078                 {
1079                         frame[target_layer]->copy_from(global_target_dst);
1080                 }
1081         }
1082         else
1083 // Read the target destination directly
1084         {
1085                 read_frame(frame[target_layer],
1086                         target_layer,
1087                         start_position,
1088                         frame_rate,
1089                         0);
1090         }
1091
1092         if(config.draw_vectors)
1093         {
1094                 draw_vectors(frame[target_layer]);
1095         }
1096
1097 #ifdef DEBUG
1098 printf("MotionHVMain::process_buffer %d\n", __LINE__);
1099 #endif
1100         return 0;
1101 }
1102
1103
1104
1105 void MotionHVMain::draw_vectors(VFrame *frame)
1106 {
1107         int w = frame->get_w();
1108         int h = frame->get_h();
1109         int global_x1, global_y1;
1110         int global_x2, global_y2;
1111         int block_x, block_y;
1112         int block_w, block_h;
1113         int block_x1, block_y1;
1114         int block_x2, block_y2;
1115         int block_x3, block_y3;
1116         int block_x4, block_y4;
1117         int search_w, search_h;
1118         int search_x1, search_y1;
1119         int search_x2, search_y2;
1120
1121
1122 // always processing global
1123 //      if(config.global)
1124         if(1)
1125         {
1126 // Get vector
1127 // Start of vector is center of previous block.
1128 // End of vector is total accumulation.
1129                 if(config.tracking_object == MotionHVScan::TRACK_SINGLE)
1130                 {
1131                         global_x1 = (int64_t)(config.block_x *
1132                                 w /
1133                                 100);
1134                         global_y1 = (int64_t)(config.block_y *
1135                                 h /
1136                                 100);
1137                         global_x2 = global_x1 + total_dx / OVERSAMPLE;
1138                         global_y2 = global_y1 + total_dy / OVERSAMPLE;
1139 //printf("MotionHVMain::draw_vectors %d %d %d %d %d %d\n", total_dx, total_dy, global_x1, global_y1, global_x2, global_y2);
1140                 }
1141                 else
1142 // Start of vector is center of previous block.
1143 // End of vector is current change.
1144                 if(config.tracking_object == MotionHVScan::PREVIOUS_SAME_BLOCK)
1145                 {
1146                         global_x1 = (int64_t)(config.block_x *
1147                                 w /
1148                                 100);
1149                         global_y1 = (int64_t)(config.block_y *
1150                                 h /
1151                                 100);
1152                         global_x2 = global_x1 + current_dx / OVERSAMPLE;
1153                         global_y2 = global_y1 + current_dy / OVERSAMPLE;
1154                 }
1155                 else
1156                 {
1157                         global_x1 = (int64_t)(config.block_x *
1158                                 w /
1159                                 100 +
1160                                 (total_dx - current_dx) /
1161                                 OVERSAMPLE);
1162                         global_y1 = (int64_t)(config.block_y *
1163                                 h /
1164                                 100 +
1165                                 (total_dy - current_dy) /
1166                                 OVERSAMPLE);
1167                         global_x2 = (int64_t)(config.block_x *
1168                                 w /
1169                                 100 +
1170                                 total_dx /
1171                                 OVERSAMPLE);
1172                         global_y2 = (int64_t)(config.block_y *
1173                                 h /
1174                                 100 +
1175                                 total_dy /
1176                                 OVERSAMPLE);
1177                 }
1178
1179                 block_x = global_x1;
1180                 block_y = global_y1;
1181                 block_w = config.global_block_w * w / 100;
1182                 block_h = config.global_block_h * h / 100;
1183                 block_x1 = block_x - block_w / 2;
1184                 block_y1 = block_y - block_h / 2;
1185                 block_x2 = block_x + block_w / 2;
1186                 block_y2 = block_y + block_h / 2;
1187                 search_w = config.global_range_w * w / 100;
1188                 search_h = config.global_range_h * h / 100;
1189                 search_x1 = block_x1 - search_w / 2;
1190                 search_y1 = block_y1 - search_h / 2;
1191                 search_x2 = block_x2 + search_w / 2;
1192                 search_y2 = block_y2 + search_h / 2;
1193
1194 // printf("MotionHVMain::draw_vectors %d %d %d %d %d %d %d %d %d %d %d %d\n",
1195 // global_x1,
1196 // global_y1,
1197 // block_w,
1198 // block_h,
1199 // block_x1,
1200 // block_y1,
1201 // block_x2,
1202 // block_y2,
1203 // search_x1,
1204 // search_y1,
1205 // search_x2,
1206 // search_y2);
1207
1208                 MotionHVScan::clamp_scan(w,
1209                         h,
1210                         &block_x1,
1211                         &block_y1,
1212                         &block_x2,
1213                         &block_y2,
1214                         &search_x1,
1215                         &search_y1,
1216                         &search_x2,
1217                         &search_y2,
1218                         1);
1219
1220 // Vector
1221                 draw_arrow(frame, global_x1, global_y1, global_x2, global_y2);
1222
1223 // Macroblock
1224                 draw_line(frame, block_x1, block_y1, block_x2, block_y1);
1225                 draw_line(frame, block_x2, block_y1, block_x2, block_y2);
1226                 draw_line(frame, block_x2, block_y2, block_x1, block_y2);
1227                 draw_line(frame, block_x1, block_y2, block_x1, block_y1);
1228
1229
1230 // Search area
1231                 draw_line(frame, search_x1, search_y1, search_x2, search_y1);
1232                 draw_line(frame, search_x2, search_y1, search_x2, search_y2);
1233                 draw_line(frame, search_x2, search_y2, search_x1, search_y2);
1234                 draw_line(frame, search_x1, search_y2, search_x1, search_y1);
1235
1236 // Block should be endpoint of motion
1237                 if(config.rotate)
1238                 {
1239                         block_x = global_x2;
1240                         block_y = global_y2;
1241                 }
1242         }
1243         else
1244         {
1245                 block_x = (int64_t)(config.block_x * w / 100);
1246                 block_y = (int64_t)(config.block_y * h / 100);
1247         }
1248
1249         block_w = config.global_block_w * w / 100;
1250         block_h = config.global_block_h * h / 100;
1251         if(config.rotate)
1252         {
1253                 float angle = total_angle * 2 * M_PI / 360;
1254                 double base_angle1 = atan((float)block_h / block_w);
1255                 double base_angle2 = atan((float)block_w / block_h);
1256                 double target_angle1 = base_angle1 + angle;
1257                 double target_angle2 = base_angle2 + angle;
1258                 double radius = sqrt(block_w * block_w + block_h * block_h) / 2;
1259                 block_x1 = (int)(block_x - cos(target_angle1) * radius);
1260                 block_y1 = (int)(block_y - sin(target_angle1) * radius);
1261                 block_x2 = (int)(block_x + sin(target_angle2) * radius);
1262                 block_y2 = (int)(block_y - cos(target_angle2) * radius);
1263                 block_x3 = (int)(block_x - sin(target_angle2) * radius);
1264                 block_y3 = (int)(block_y + cos(target_angle2) * radius);
1265                 block_x4 = (int)(block_x + cos(target_angle1) * radius);
1266                 block_y4 = (int)(block_y + sin(target_angle1) * radius);
1267
1268                 draw_line(frame, block_x1, block_y1, block_x2, block_y2);
1269                 draw_line(frame, block_x2, block_y2, block_x4, block_y4);
1270                 draw_line(frame, block_x4, block_y4, block_x3, block_y3);
1271                 draw_line(frame, block_x3, block_y3, block_x1, block_y1);
1272
1273
1274 // Center
1275                 if(!config.global)
1276                 {
1277                         draw_line(frame, block_x, block_y - 5, block_x, block_y + 6);
1278                         draw_line(frame, block_x - 5, block_y, block_x + 6, block_y);
1279                 }
1280         }
1281 }
1282
1283
1284 MotionHvVVFrame::MotionHvVVFrame(VFrame *vfrm, int n)
1285  : VFrame(vfrm->get_data(), -1, vfrm->get_y()-vfrm->get_data(),
1286         vfrm->get_u()-vfrm->get_data(), vfrm->get_v()-vfrm->get_data(),
1287         vfrm->get_w(), vfrm->get_h(), vfrm->get_color_model(),
1288         vfrm->get_bytes_per_line())
1289 {
1290         this->n = n;
1291 }
1292
1293 int MotionHvVVFrame::draw_pixel(int x, int y)
1294 {
1295         VFrame::draw_pixel(x+0, y+0);
1296         for( int i=1; i<n; ++i ) {
1297                 VFrame::draw_pixel(x-i, y-i);
1298                 VFrame::draw_pixel(x+i, y+i);
1299         }
1300         return 0;
1301 }
1302
1303 void MotionHVMain::draw_line(VFrame *frame, int x1, int y1, int x2, int y2)
1304 {
1305         int iw = frame->get_w(), ih = frame->get_h();
1306         int mx = iw > ih ? iw : ih;
1307         int n = mx/800 + 1;
1308         MotionHvVVFrame vfrm(frame, n);
1309         vfrm.set_pixel_color(WHITE);
1310         int m = 2;  while( m < n ) m <<= 1;
1311         vfrm.set_stiple(2*m);
1312         vfrm.draw_line(x1,y1, x2,y2);
1313 }
1314
1315 #define ARROW_SIZE 10
1316 void MotionHVMain::draw_arrow(VFrame *frame, int x1, int y1, int x2, int y2)
1317 {
1318         double angle = atan((float)(y2 - y1) / (float)(x2 - x1));
1319         double angle1 = angle + (float)145 / 360 * 2 * 3.14159265;
1320         double angle2 = angle - (float)145 / 360 * 2 * 3.14159265;
1321         int x3;
1322         int y3;
1323         int x4;
1324         int y4;
1325         if(x2 < x1)
1326         {
1327                 x3 = x2 - (int)(ARROW_SIZE * cos(angle1));
1328                 y3 = y2 - (int)(ARROW_SIZE * sin(angle1));
1329                 x4 = x2 - (int)(ARROW_SIZE * cos(angle2));
1330                 y4 = y2 - (int)(ARROW_SIZE * sin(angle2));
1331         }
1332         else
1333         {
1334                 x3 = x2 + (int)(ARROW_SIZE * cos(angle1));
1335                 y3 = y2 + (int)(ARROW_SIZE * sin(angle1));
1336                 x4 = x2 + (int)(ARROW_SIZE * cos(angle2));
1337                 y4 = y2 + (int)(ARROW_SIZE * sin(angle2));
1338         }
1339
1340 // Main vector
1341         draw_line(frame, x1, y1, x2, y2);
1342 //      draw_line(frame, x1, y1 + 1, x2, y2 + 1);
1343
1344 // Arrow line
1345         if(abs(y2 - y1) || abs(x2 - x1)) draw_line(frame, x2, y2, x3, y3);
1346 //      draw_line(frame, x2, y2 + 1, x3, y3 + 1);
1347 // Arrow line
1348         if(abs(y2 - y1) || abs(x2 - x1)) draw_line(frame, x2, y2, x4, y4);
1349 //      draw_line(frame, x2, y2 + 1, x4, y4 + 1);
1350 }
1351
1352
1353
1354
1355
1356
1357