From f110e7626d433b4724befe0871a3a35f9f81f264 Mon Sep 17 00:00:00 2001 From: Good Guy Date: Thu, 19 Jan 2017 16:19:52 -0700 Subject: [PATCH] smooth lines, motion51, opengl pbuffer bit typo, misc fixes --- cinelerra-5.1/cinelerra/affine.C | 5 + cinelerra-5.1/cinelerra/file.C | 1 + cinelerra-5.1/cinelerra/fileffmpeg.C | 26 +- cinelerra-5.1/cinelerra/fileffmpeg.h | 3 +- cinelerra-5.1/cinelerra/pluginclient.C | 13 + cinelerra-5.1/cinelerra/pluginclient.h | 2 + cinelerra-5.1/ffmpeg/video/h264.mp4 | 22 - cinelerra-5.1/guicast/bctextbox.C | 16 +- cinelerra-5.1/guicast/bcwindow3d.C | 6 +- cinelerra-5.1/guicast/bcwindowbase.h | 1 + cinelerra-5.1/guicast/clip.h | 23 +- cinelerra-5.1/guicast/vframe.C | 347 +++++-- cinelerra-5.1/guicast/vframe.h | 10 +- cinelerra-5.1/plugin_defs | 5 +- cinelerra-5.1/plugins/Makefile | 1 + .../plugins/bluebanana/bluebananawindow.C | 35 +- cinelerra-5.1/plugins/echocancel/echocancel.C | 2 +- cinelerra-5.1/plugins/motion51/Makefile | 13 + cinelerra-5.1/plugins/motion51/motion51.C | 963 ++++++++++++++++++ cinelerra-5.1/plugins/motion51/motion51.h | 181 ++++ cinelerra-5.1/plugins/motion51/motion51.inc | 21 + .../plugins/motion51/motionwindow51.C | 282 +++++ .../plugins/motion51/motionwindow51.h | 125 +++ cinelerra-5.1/plugins/motion51/picon.png | Bin 0 -> 5999 bytes 24 files changed, 1961 insertions(+), 142 deletions(-) create mode 100644 cinelerra-5.1/plugins/motion51/Makefile create mode 100644 cinelerra-5.1/plugins/motion51/motion51.C create mode 100644 cinelerra-5.1/plugins/motion51/motion51.h create mode 100644 cinelerra-5.1/plugins/motion51/motion51.inc create mode 100644 cinelerra-5.1/plugins/motion51/motionwindow51.C create mode 100644 cinelerra-5.1/plugins/motion51/motionwindow51.h create mode 100644 cinelerra-5.1/plugins/motion51/picon.png diff --git a/cinelerra-5.1/cinelerra/affine.C b/cinelerra-5.1/cinelerra/affine.C index 8c603bd6..09f81f2e 100644 --- a/cinelerra-5.1/cinelerra/affine.C +++ b/cinelerra-5.1/cinelerra/affine.C @@ -372,6 +372,11 @@ void AffineUnit::process_package(LoadPackage *package) server->use_opengl) { #ifdef HAVE_GL + out_x1 -= pivot_offset_x; out_y1 -= pivot_offset_y; + out_x2 -= pivot_offset_x; out_y2 -= pivot_offset_y; + out_x3 -= pivot_offset_x; out_y3 -= pivot_offset_y; + out_x4 -= pivot_offset_x; out_y4 -= pivot_offset_y; + server->output->to_texture(); server->output->enable_opengl(); server->output->init_screen(); diff --git a/cinelerra-5.1/cinelerra/file.C b/cinelerra-5.1/cinelerra/file.C index fbc03adf..a502b9f2 100644 --- a/cinelerra-5.1/cinelerra/file.C +++ b/cinelerra-5.1/cinelerra/file.C @@ -1525,6 +1525,7 @@ int File::supports_audio(int format) case FILE_AIFF: case FILE_SND: case FILE_FFMPEG: + case FILE_RAWDV: return 1; } return 0; diff --git a/cinelerra-5.1/cinelerra/fileffmpeg.C b/cinelerra-5.1/cinelerra/fileffmpeg.C index 94205be8..4d7c713b 100644 --- a/cinelerra-5.1/cinelerra/fileffmpeg.C +++ b/cinelerra-5.1/cinelerra/fileffmpeg.C @@ -111,6 +111,14 @@ int FFMpegVideoQuality::handle_event() return ret; } +void FileFFMPEG::set_parameters(char *cp, int len, const char *bp) +{ + char *ep = cp + len-2, ch = 0; + while( cp < ep && *bp != 0 ) { ch = *bp++; *cp++ = ch; } + if( ch != '\n' ) *cp++ = '\n'; + *cp = 0; +} + void FileFFMPEG::get_parameters(BC_WindowBase *parent_window, Asset *asset, BC_WindowBase *&format_window, int audio_options, int video_options) @@ -120,7 +128,8 @@ void FileFFMPEG::get_parameters(BC_WindowBase *parent_window, format_window = window; window->create_objects(); if( !window->run_window() ) - strcpy(asset->ff_audio_options, window->audio_options->get_text()); + set_parameters(asset->ff_audio_options, sizeof(asset->ff_audio_options), + window->audio_options->get_text()); delete window; } else if(video_options) { @@ -128,7 +137,8 @@ void FileFFMPEG::get_parameters(BC_WindowBase *parent_window, format_window = window; window->create_objects(); if( !window->run_window() ) - strcpy(asset->ff_video_options, window->video_options->get_text()); + set_parameters(asset->ff_video_options, sizeof(asset->ff_video_options), + window->video_options->get_text()); delete window; } } @@ -476,12 +486,6 @@ FFAudioOptions::FFAudioOptions(FFMPEGConfigAudio *audio_popup, this->audio_popup = audio_popup; } -int FFAudioOptions::handle_event() -{ - strcpy(audio_popup->asset->ff_audio_options, get_text()); - return 1; -} - FFMPEGConfigAudioPopup::FFMPEGConfigAudioPopup(FFMPEGConfigAudio *popup, int x, int y) : BC_PopupTextBox(popup, &popup->presets, popup->asset->acodec, x, y, 300, 300) @@ -643,12 +647,6 @@ FFVideoOptions::FFVideoOptions(FFMPEGConfigVideo *video_popup, this->video_popup = video_popup; } -int FFVideoOptions::handle_event() -{ - strcpy(video_popup->asset->ff_video_options, get_text()); - return 1; -} - FFMPEGConfigVideoPopup::FFMPEGConfigVideoPopup(FFMPEGConfigVideo *popup, int x, int y) : BC_PopupTextBox(popup, &popup->presets, popup->asset->vcodec, x, y, 300, 300) diff --git a/cinelerra-5.1/cinelerra/fileffmpeg.h b/cinelerra-5.1/cinelerra/fileffmpeg.h index a4aea297..63de93c7 100644 --- a/cinelerra-5.1/cinelerra/fileffmpeg.h +++ b/cinelerra-5.1/cinelerra/fileffmpeg.h @@ -31,6 +31,7 @@ public: static void ff_lock(const char *cp=0); static void ff_unlock(); + static void set_parameters(char *cp, int len, const char *bp); static void get_parameters(BC_WindowBase *parent_window,Asset *asset, BC_WindowBase *&format_window,int audio_options,int video_options); static int check_sig(Asset *asset); @@ -135,7 +136,6 @@ class FFAudioOptions : public BC_ScrollTextBox public: FFAudioOptions(FFMPEGConfigAudio *audio_popup, int x, int y, int w, int rows, int size, char *text); - int handle_event(); FFMPEGConfigAudio *audio_popup; }; @@ -184,7 +184,6 @@ class FFVideoOptions : public BC_ScrollTextBox public: FFVideoOptions(FFMPEGConfigVideo *video_popup, int x, int y, int w, int rows, int size, char *text); - int handle_event(); FFMPEGConfigVideo *video_popup; }; diff --git a/cinelerra-5.1/cinelerra/pluginclient.C b/cinelerra-5.1/cinelerra/pluginclient.C index 2f5d0e37..6af3a313 100644 --- a/cinelerra-5.1/cinelerra/pluginclient.C +++ b/cinelerra-5.1/cinelerra/pluginclient.C @@ -24,17 +24,22 @@ #include "bcsignals.h" #include "clip.h" #include "condition.h" +#include "edits.h" +#include "edit.h" #include "edl.h" #include "edlsession.h" #include "file.h" #include "filesystem.h" +#include "indexable.h" #include "language.h" #include "localsession.h" #include "mainundo.h" #include "mwindow.h" +#include "plugin.h" #include "pluginclient.h" #include "pluginserver.h" #include "preferences.h" +#include "track.h" #include "transportque.inc" @@ -683,6 +688,14 @@ double PluginClient::get_project_framerate() return server->get_project_framerate(); } +const char *PluginClient::get_source_path() +{ + int64_t source_position = server->plugin->startproject; + Edit *edit = server->plugin->track->edits->editof(source_position,PLAY_FORWARD,0); + Indexable *indexable = edit ? edit->get_source() : 0; + return indexable ? indexable->path : 0; +} + void PluginClient::update_display_title() { diff --git a/cinelerra-5.1/cinelerra/pluginclient.h b/cinelerra-5.1/cinelerra/pluginclient.h index 83e0eeed..260a0eda 100644 --- a/cinelerra-5.1/cinelerra/pluginclient.h +++ b/cinelerra-5.1/cinelerra/pluginclient.h @@ -414,6 +414,8 @@ public: int get_project_samplerate(); // get framerate of EDL double get_project_framerate(); +// get asset path + const char *get_source_path(); // Total number of processors - 1 int get_project_smp(); int get_aspect_ratio(float &aspect_w, float &aspect_h); diff --git a/cinelerra-5.1/ffmpeg/video/h264.mp4 b/cinelerra-5.1/ffmpeg/video/h264.mp4 index 94860793..c4daad77 100644 --- a/cinelerra-5.1/ffmpeg/video/h264.mp4 +++ b/cinelerra-5.1/ffmpeg/video/h264.mp4 @@ -1,23 +1 @@ mp4 libx264 -partitions=i8x8,i4x4,p8x8,b8x8 -me_method=hex -subq=7 -me_range=16 -g=250 -keyint_min=25 -sc_threshold=40 -i_qfactor=0.71 -b_strategy=1 -qcomp=0.6 -qmin=10 -qmax=51 -qdiff=4 -bf=3 -refs=3 -directpred=1 -trellis=1 -mixed-refs=1 -weightp=2 -8x8dct=1 -fast-pskip=1 -mbtree=1 diff --git a/cinelerra-5.1/guicast/bctextbox.C b/cinelerra-5.1/guicast/bctextbox.C index f8777993..c76f67ef 100644 --- a/cinelerra-5.1/guicast/bctextbox.C +++ b/cinelerra-5.1/guicast/bctextbox.C @@ -1231,21 +1231,23 @@ int BC_TextBox::keypress_event() int wlen = -1; switch( last_keypress ) { //unicode active acitons - case ESC: { - unicode_active = -1; - result = 1; - wlen = 0; - break; } case RETURN: { for( int i=highlight_letter1+1; i 0) { diff --git a/cinelerra-5.1/guicast/bcwindow3d.C b/cinelerra-5.1/guicast/bcwindow3d.C index b3302fa5..d3da09ea 100644 --- a/cinelerra-5.1/guicast/bcwindow3d.C +++ b/cinelerra-5.1/guicast/bcwindow3d.C @@ -72,7 +72,7 @@ GLXFBConfig *BC_WindowBase::glx_window_fb_configs() if( !glx_fbcfgs_window ) { int fb_attrs[] = { GLX_CONFIG_CAVEAT, GLX_SLOW_CONFIG, - GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT | GLX_PBUFFER | GLX_PIXMAP_BIT, + GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT | GLX_PBUFFER_BIT | GLX_PIXMAP_BIT, GLX_DOUBLEBUFFER, True, GLX_RENDER_TYPE, GLX_RGBA_BIT, GLX_ACCUM_RED_SIZE, 1, @@ -118,7 +118,7 @@ GLXFBConfig *BC_WindowBase::glx_pbuffer_fb_configs() if( !glx_fbcfgs_pbuffer ) { int fb_attrs[] = { GLX_CONFIG_CAVEAT, GLX_SLOW_CONFIG, - GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT | GLX_PBUFFER | GLX_PIXMAP_BIT, + GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT | GLX_PBUFFER_BIT | GLX_PIXMAP_BIT, GLX_DOUBLEBUFFER, True, //False, GLX_RENDER_TYPE, GLX_RGBA_BIT, GLX_ACCUM_RED_SIZE, 1, @@ -143,7 +143,7 @@ GLXFBConfig *BC_WindowBase::glx_pixmap_fb_configs() if( !glx_fbcfgs_pixmap ) { static int fb_attrs[] = { GLX_CONFIG_CAVEAT, GLX_SLOW_CONFIG, - GLX_DRAWABLE_TYPE, GLX_PIXMAP_BIT | GLX_PBUFFER, + GLX_DRAWABLE_TYPE, GLX_PIXMAP_BIT | GLX_PBUFFER_BIT, GLX_DOUBLEBUFFER, True, //False, GLX_RENDER_TYPE, GLX_RGBA_BIT, GLX_RED_SIZE, 8, diff --git a/cinelerra-5.1/guicast/bcwindowbase.h b/cinelerra-5.1/guicast/bcwindowbase.h index c2ed7abc..2acfe5c3 100644 --- a/cinelerra-5.1/guicast/bcwindowbase.h +++ b/cinelerra-5.1/guicast/bcwindowbase.h @@ -201,6 +201,7 @@ public: // Wait until event loop is running void init_wait(); int is_running() { return window_running; } + int is_hidden() { return hidden; } // Check if a hardware accelerated colormodel is available and reserve it int accel_available(int color_model, int lock_it); void get_input_context(); diff --git a/cinelerra-5.1/guicast/clip.h b/cinelerra-5.1/guicast/clip.h index 44a5f450..3b51b601 100644 --- a/cinelerra-5.1/guicast/clip.h +++ b/cinelerra-5.1/guicast/clip.h @@ -19,8 +19,8 @@ * */ -#ifndef CLIP_H -#define CLIP_H +#ifndef __CLIP_H__ +#define __CLIP_H__ // Math macros #undef SQR @@ -43,4 +43,23 @@ #define TO_RAD(x) ((x) * 2 * M_PI / 360) #define TO_DEG(x) ((x) * 360 / 2 / M_PI) +static inline int bclip(int &iv, int imn, int imx) { + return iv < imn ? imn : iv > imx ? imx : iv; +} +static inline float bclip(float &fv, float fmn, float fmx) { + return fv < fmn ? fmn : fv > fmx ? fmx : fv; +} +static inline double bclip(double &dv, double dmn, double dmx) { + return dv < dmn ? dmn : dv > dmx ? dmx : dv; +} +static inline void bclamp(int &iv, int imn, int imx) { + if( iv < imn ) iv = imn; else if( iv > imx ) iv = imx; +} +static inline void bclamp(float &fv, float fmn, float fmx) { + if( fv < fmn ) fv = fmn; else if( fv > fmx ) fv = fmx; +} +static inline void bclamp(double &dv, double dmn, double dmx) { + if( dv < dmn ) dv = dmn; else if( dv > dmx ) dv = dmx; +} + #endif diff --git a/cinelerra-5.1/guicast/vframe.C b/cinelerra-5.1/guicast/vframe.C index e6f0d9d5..941ec09e 100644 --- a/cinelerra-5.1/guicast/vframe.C +++ b/cinelerra-5.1/guicast/vframe.C @@ -1371,58 +1371,282 @@ void VFrame::draw_pixel(int x, int y) } +// Bresenham's void VFrame::draw_line(int x1, int y1, int x2, int y2) { - int w = labs(x2 - x1); - int h = labs(y2 - y1); -//printf("FindObjectMain::draw_line 1 %d %d %d %d\n", x1, y1, x2, y2); + if( y1 > y2 ) { + int tx = x1; x1 = x2; x2 = tx; + int ty = y1; y1 = y2; y2 = ty; + } - if(!w && !h) - { - draw_pixel(x1, y1); + int x = x1, y = y1; + int dx = x2-x1, dy = y2-y1; + int dx2 = 2*dx, dy2 = 2*dy; + if( dx < 0 ) dx = -dx; + int r = dx > dy ? dx : dy, n = r; + int dir = 0; + if( dx2 < 0 ) dir += 1; + if( dy >= dx ) { + if( dx2 >= 0 ) do { /* +Y, +X */ + draw_pixel(x, y++); + if( (r -= dx2) < 0 ) { r += dy2; ++x; } + } while( --n >= 0 ); + else do { /* +Y, -X */ + draw_pixel(x, y++); + if( (r += dx2) < 0 ) { r += dy2; --x; } + } while( --n >= 0 ); } - else - if(w > h) - { -// Flip coordinates so x1 < x2 - if(x2 < x1) - { - y2 ^= y1; - y1 ^= y2; - y2 ^= y1; - x1 ^= x2; - x2 ^= x1; - x1 ^= x2; + else { + if( dx2 >= 0 ) do { /* +X, +Y */ + draw_pixel(x++, y); + if( (r -= dy2) < 0 ) { r += dx2; ++y; } + } while( --n >= 0 ); + else do { /* -X, +Y */ + draw_pixel(x--, y); + if( (r -= dy2) < 0 ) { r -= dx2; ++y; } + } while( --n >= 0 ); + } +} + +// g++ -dD -E - < /dev/null | grep DBL_EPSILON +#ifndef __DBL_EPSILON__ +#define __DBL_EPSILON__ ((double)2.22044604925031308085e-16L) +#endif +// weakest fraction * graphics integer range +#define RND_EPSILON (__DBL_EPSILON__*65536) + +class smooth_line { + int rnd(double v) { return round(v)+RND_EPSILON; } + VFrame *vframe; +public: + int sx, sy, ex, ey; /* current point, end point */ + int xs, ys; /* x/y quadrant sign -1/1 */ + int64_t A, B, C; /* quadratic coefficients */ + int64_t r, dx, dy; /* residual, dr/dx and dr/dy */ + int xmxx, xmxy; /* x,y at apex */ + int done; + + void init0(int x1,int y1, int x2,int y2, int x3,int y3, int top); + void init1(int x1,int y1, int x2,int y2, int x3,int y3); + int64_t rx() { return r + xs*8*dx + 4*A; } + void moveX(int64_t r) { + dx += xs*A; dy -= xs*B; + this->r = r; sx += xs; + } + int64_t ry() { return r + 8*dy + 4*C; } + void moveY(int64_t r) { + dx -= B; dy += C; + this->r = r; ++sy; + } + void draw(); + + smooth_line(VFrame *vframe) { this->vframe = vframe; this->done = 0; } +}; + + +void smooth_line::draw() +{ + if( done ) return; + if( abs(dy) >= abs(dx) ) { + if( xs*(sx-xmxx) >= 0 ) { + if( ys > 0 ) { done = 1; return; } + if( dy < 0 || ry() < 0 ) { moveY(ry()); goto xit; } + xmxx = ex; xmxy = ey; + ys = 1; xs = -xs; } - int numerator = y2 - y1; - int denominator = x2 - x1; - for(int i = x1; i <= x2; i++) - { - int y = y1 + (int64_t)(i - x1) * (int64_t)numerator / (int64_t)denominator; - draw_pixel(i, y); + moveX(rx()); + int64_t rr = ry(); + if( abs(rr) < abs(r) ) + moveY(rr); + } + else { + if( sy >= xmxy ) { + if( ys > 0 ) { done = 1; return; } + xmxx = ex; xmxy = ey; + ys = 1; xs = -xs; } + moveY(ry()); + int64_t rr = rx(); + if( abs(rr) < abs(r) ) + moveX(rr); + } +xit: vframe->draw_pixel(sx, sy); +} + +void VFrame::draw_smooth(int x1, int y1, int x2, int y2, int x3, int y3) +{ + if( (x1 == x2 && y1 == y2) || (x2 == x3 && y2 == y3) ) + draw_line(x1,y1, x3,y3); + else if( x1 == x3 && y1 == y3 ) + draw_line(x1,y1, x2,y2); + else if( (x2-x1) * (y2-y3) == (x2-x3) * (y2-y1) ) { + // co-linear, draw line from min to max + if( x1 < x3 ) { + if( x2 < x1 ) { x1 = x2; y1 = y2; } + if( x2 > x3 ) { x3 = x2; y3 = y2; } + } + else { + if( x2 > x1 ) { x1 = x2; y1 = y2; } + if( x2 < x3 ) { x3 = x2; y3 = y2; } + } + draw_line(x1,y1, x3,y3); } else - { -// Flip coordinates so y1 < y2 - if(y2 < y1) - { - y2 ^= y1; - y1 ^= y2; - y2 ^= y1; - x1 ^= x2; - x2 ^= x1; - x1 ^= x2; + smooth_draw(x1, y1, x2, y2, x3, y3); +} + +/* + Non-Parametric Smooth Curve Generation. Don Kelly 1984 + + P+-----+Q'= virtual + / / origin + / / + Q+-----+R + + Let the starting point be P. the ending point R. and the tangent vertex Q. + A general point Z on the curve is then + Z = (P + R - Q) + (Q - P) sin t + (Q - R) cos t + + Expanding the Cartesian coordinates around (P + R - Q) gives + [x y] = Z - (P + R - Q) + [a c] = Q - P + [b d] = Q - R + x = a*sin(t) + b*cos(t) + y = c*sin(t) + d*cos(t) + + from which t can now be eliminated via + c*x - a*y = (c*b - a*d)*cos(t) + d*x - b*y = (a*d - c*b)*sin(t) + + giving the Cartesian equation for the ellipse as + f(x, y) = (c*x - a*y)**2 + (d*x - b*y)**2 - (a*d - c*b)**2 = 0 + + or: f(x, y) = A*x**2 - 2*B*x*y + C*y**2 + B**2 - A*C = 0 + where: A = c**2 + d**2, B = a*c + b*d, C = a**2 + b**2 + + The maximum y extent of the ellipse may now be derived as follows: + let df/dx = 0, 2*A*x = 2*B*y, x = y*B/A + f(x, y) == B**2 * y**2 / A - 2*B**2 * y**2 / A + C*y**2 + B**2 - A*C = 0 + (A*C - B**2)*y = (A*C - B**2)*A + max x = sqrt(C), at y = B/sqrt(C) + max y = sqrt(A), at x = B/sqrt(A) + + */ + + +/* x1,y1 = P, x2,y2 = Q, x3,y3=R, + * draw from P to Q to R if top=0 + * or from P to (x,ymax) if top>0 + * or from Q to (x,ymax) if top<0 + */ +void smooth_line::init0(int x1,int y1, int x2,int y2, int x3,int y3, int top) +{ + int x0 = x1+x3-x2, y0 = y1+y3-y2; // Q' + + int a = x2-x1, c = y2-y1; + int b = x2-x3, d = y2-y3; + A = c*c + d*d; C = a*a + b*b; B = a*c + b*d; + + sx = top >= 0 ? x1 : x3; + sy = top >= 0 ? y1 : y3; + xs = x2 > sx || (x2==sx && (x1+x3-sx)>=x2) ? 1 : -1; + int64_t px = sx-x0, py = sy-y0; + dx = A*px - B*py; dy = C*py - B*px; + r = 0; + + if( top ) { + double ymy = sqrt(A), ymx = B/ymy; + ex = x0 + rnd(ymx); + ey = y0 + rnd(ymy); + } + else { + ex = x3; ey = y3; + } + + ys = a*b > 0 && (!top || top*xs*(b*c - a*d) > 0) ? -1 : 1; + if( ys < 0 ) { + double xmx = xs*sqrt(C), xmy = B/xmx; + xmxx = x0 + rnd(xmx); + xmxy = y0 + rnd(xmy); + } + else { + xmxx = ex; xmxy = ey; + } +} + +/* x1,y1 = P, x2,y2 = Q, x3,y3=R, + * draw from (x,ymax) to P + */ +void smooth_line::init1(int x1,int y1, int x2,int y2, int x3,int y3) +{ + int x0 = x1+x3-x2, y0 = y1+y3-y2; // Q' + + int a = x2-x1, c = y2-y1; + int b = x2-x3, d = y2-y3; + A = c*c + d*d; C = a*a + b*b; B = a*c + b*d; + + double ymy = -sqrt(A), ymx = B/ymy; + int64_t px = rnd(ymx), py = rnd(ymy); + sx = x0 + px; ex = x1; + sy = y0 + py; ey = y1; + xs = x2 > x1 || (x2==x1 && x3>=x2) ? 1 : -1; + dx = A*px - B*py; dy = C*py - B*px; + r = 4 * (A*px*px - 2*B*px*py + C*py*py + B*B - A*C); + + ys = a*b > 0 && xs*(b*c - a*d) < 0 ? -1 : 1; + if( ys < 0 ) { + double xmx = xs*sqrt(C), xmy = B/xmx; + xmxx = x0 + rnd(xmx); + xmxy = y0 + rnd(xmy); + } + else { + xs = -xs; + xmxx = ex; xmxy = ey; + } + if( xs > 0 ) + vframe->draw_pixel(sx, sy); + while( xs*(sx-xmxx) < 0 && (xs*dx < 0 || rx() < 0) ) { + moveX(rx()); + vframe->draw_pixel(sx, sy); + } +} + + +void VFrame::smooth_draw(int x1, int y1, int x2, int y2, int x3, int y3) +{ +//printf("p smooth_draw( %d,%d, %d,%d, %d,%d )\n", x1,y1,x2,y2,x3,y3); + if( y1 > y3 ) { // make y3 >= y1 + int xt = x1; x1 = x3; x3 = xt; + int yt = y1; y1 = y3; y3 = yt; + } + if( y1 > y2 && y3 > y2 ) { + smooth_line lt(this), rt(this); // Q on bottom + lt.init1(x1, y1, x2, y2, x3, y3); + rt.init1(x3, y3, x2, y2, x1, y1); + while( !lt.done || !rt.done ) { + lt.draw(); + rt.draw(); } - int numerator = x2 - x1; - int denominator = y2 - y1; - for(int i = y1; i <= y2; i++) - { - int x = x1 + (int64_t)(i - y1) * (int64_t)numerator / (int64_t)denominator; - draw_pixel(x, i); + } + else if( y1 < y2 && y3 < y2 ) { + smooth_line lt(this), rt(this); // Q on top + lt.init0(x1, y1, x2, y2, x3, y3, 1); + draw_pixel(lt.sx, lt.sy); + rt.init0(x1, y1, x2, y2, x3, y3, -1); + draw_pixel(rt.sx, rt.sy); + while( !lt.done || !rt.done ) { + lt.draw(); + rt.draw(); + } + } + else { + smooth_line pt(this); // Q in between + pt.init0(x1, y1, x2, y2, x3, y3, 0); + draw_pixel(pt.sx, pt.sy); + while( !pt.done ) { + pt.draw(); } } -//printf("FindObjectMain::draw_line 2\n"); } void VFrame::draw_rect(int x1, int y1, int x2, int y2) @@ -1433,30 +1657,16 @@ void VFrame::draw_rect(int x1, int y1, int x2, int y2) draw_line(x1, y2 - 1, x1, y1 + 1); } -#define ARROW_SIZE 10 -void VFrame::draw_arrow(int x1, int y1, int x2, int y2) +void VFrame::draw_arrow(int x1, int y1, int x2, int y2, int sz) { double angle = atan((float)(y2 - y1) / (float)(x2 - x1)); - double angle1 = angle + (float)145 / 360 * 2 * 3.14159265; - double angle2 = angle - (float)145 / 360 * 2 * 3.14159265; - int x3; - int y3; - int x4; - int y4; - if(x2 < x1) - { - x3 = x2 - (int)(ARROW_SIZE * cos(angle1)); - y3 = y2 - (int)(ARROW_SIZE * sin(angle1)); - x4 = x2 - (int)(ARROW_SIZE * cos(angle2)); - y4 = y2 - (int)(ARROW_SIZE * sin(angle2)); - } - else - { - x3 = x2 + (int)(ARROW_SIZE * cos(angle1)); - y3 = y2 + (int)(ARROW_SIZE * sin(angle1)); - x4 = x2 + (int)(ARROW_SIZE * cos(angle2)); - y4 = y2 + (int)(ARROW_SIZE * sin(angle2)); - } + double angle1 = angle + (float)145 / 360 * 2 * M_PI; + double angle2 = angle - (float)145 / 360 * 2 * M_PI; + int s = x2 < x1 ? -1 : 1; + int x3 = x2 + s * (int)(sz * cos(angle1)); + int y3 = y2 + s * (int)(sz * sin(angle1)); + int x4 = x2 + s * (int)(sz * cos(angle2)); + int y4 = y2 + s * (int)(sz * sin(angle2)); // Main vector draw_line(x1, y1, x2, y2); @@ -1468,6 +1678,15 @@ void VFrame::draw_arrow(int x1, int y1, int x2, int y2) if(abs(y2 - y1) || abs(x2 - x1)) draw_line(x2, y2, x4, y4); } - +void VFrame::draw_x(int x, int y, int sz) +{ + draw_line(x-sz,y-sz, x+sz,y+sz); + draw_line(x+sz,y-sz, x-sz,y+sz); +} +void VFrame::draw_t(int x, int y, int sz) +{ + draw_line(x,y-sz, x,y+sz); + draw_line(x+sz,y, x-sz,y); +} diff --git a/cinelerra-5.1/guicast/vframe.h b/cinelerra-5.1/guicast/vframe.h index adb8d792..73ec70da 100644 --- a/cinelerra-5.1/guicast/vframe.h +++ b/cinelerra-5.1/guicast/vframe.h @@ -344,10 +344,14 @@ public: // This clears the stacks and the param table void clear_stacks(); - void draw_rect(int x1, int y1, int x2, int y2); - void draw_line(int x1, int y1, int x2, int y2); void draw_pixel(int x, int y); - void draw_arrow(int x1, int y1, int x2, int y2); + void draw_line(int x1, int y1, int x2, int y2); + void draw_smooth(int x1, int y1, int x2, int y2, int x3, int y3); + void smooth_draw(int x1, int y1, int x2, int y2, int x3, int y3); + void draw_rect(int x1, int y1, int x2, int y2); + void draw_arrow(int x1, int y1, int x2, int y2, int sz=10); + void draw_x(int x1, int y1, int sz=2); + void draw_t(int x1, int y1, int sz=2); // 3D scene graphs // Not integrated with shmem because that only affects codecs diff --git a/cinelerra-5.1/plugin_defs b/cinelerra-5.1/plugin_defs index d8fdbbd0..0530ee08 100644 --- a/cinelerra-5.1/plugin_defs +++ b/cinelerra-5.1/plugin_defs @@ -36,8 +36,9 @@ audio_tools := audioscope cdripper compressor dcoffset delayaudio \ plugin_dirs += video_tools video_tools := blur decimate delayvideo denoisemjpeg denoisevideo downsample \ fieldframe flash framefield freezeframe greycstoration interpolatepixels \ - interpolatevideo invertvideo linearblur loopvideo motion2 motionblur \ - motion motion-cv motion-hv overlay radialblur reframe reframert reroute \ + interpolatevideo invertvideo linearblur loopvideo \ + motion2 motionblur motion motion-cv motion-hv motion51 \ + overlay radialblur reframe reframert reroute \ reversevideo seltempavg sharpen spectrogram svg titler timeavg timefront \ unsharp videoscope wave zoomblur diff --git a/cinelerra-5.1/plugins/Makefile b/cinelerra-5.1/plugins/Makefile index 5bac9134..c2000234 100644 --- a/cinelerra-5.1/plugins/Makefile +++ b/cinelerra-5.1/plugins/Makefile @@ -78,6 +78,7 @@ DIRS = \ loopaudio \ loopvideo \ motion \ + motion51 \ motion-cv \ motion-hv \ motion2point \ diff --git a/cinelerra-5.1/plugins/bluebanana/bluebananawindow.C b/cinelerra-5.1/plugins/bluebanana/bluebananawindow.C index 8ef90b55..191b635c 100644 --- a/cinelerra-5.1/plugins/bluebanana/bluebananawindow.C +++ b/cinelerra-5.1/plugins/bluebanana/bluebananawindow.C @@ -1350,10 +1350,9 @@ class BluebananaAAReadout : public BB_Tumble { class BluebananaAASlider : public BluebananaSliderSingle { public: - int hidden; BluebananaAASlider(BluebananaMain *plugin, BluebananaWindow *gui, int x, int y, int w, int h) - : BluebananaSliderSingle(plugin,gui,x,y,w,h,0,100) { hidden = 0; } + : BluebananaSliderSingle(plugin,gui,x,y,w,h,0,100) {} virtual int handle_event() { plugin->config.Aadj_val = val; return 1; @@ -1365,11 +1364,11 @@ public: void update(){ val = plugin->config.Aadj_val; if( BC_CModels::has_alpha(plugin->colormodel) ) { - if( hidden ) { show_window(); hidden = 0; } + if( is_hidden() ) show_window(); }else{ - if( !hidden ) { hide_window(); hidden = 1; } + if( !is_hidden() ) hide_window(); } - if( hidden ) return; + if( is_hidden() ) return; highlight = plugin->config.active && plugin->config.Aadj_active; gui->Aadj_readout->update(plugin->config.Aadj_val); gui->slider_labels[11]->set_color(highlight && plugin->config.Aadj_val != 100 ? @@ -1692,13 +1691,11 @@ public: class BluebananaAAActive : public BC_CheckBox { public: - int hidden; BluebananaAAActive(BluebananaMain *plugin, BluebananaWindow *gui) : BC_CheckBox(-1, -1, &plugin->config.Aadj_active, ""){ this->plugin = plugin; this->gui = gui; - hidden = 0; } virtual int handle_event(){ plugin->config.Aadj_active = @@ -1709,11 +1706,11 @@ public: void update(){ this->BC_CheckBox::update(plugin->config.Aadj_active,1); if( BC_CModels::has_alpha(plugin->colormodel) ) { - if( hidden ) { show_window(); hidden = 0; } + if( is_hidden() ) show_window(); }else{ - if( !hidden ) { hide_window(); hidden = 1; } + if( !is_hidden() ) hide_window(); } - if( hidden ) return; + if( is_hidden() ) return; gui->Aadj_slider->update(); } BluebananaMain *plugin; @@ -1848,7 +1845,6 @@ public: this->y=-1; gui->add_subwindow(this->label); gui->add_subwindow(this); - hidden = -1; } virtual int handle_event(){ plugin->config.capture_mask=get_value(); @@ -1887,23 +1883,21 @@ public: break; } - if(hideme && hidden!=1){ + if(hideme && !is_hidden()){ hide_window(); label->hide_window(); gui->set_color(get_resources()->get_bg_color()); gui->draw_box(x,y,w,h); gui->set_color(get_resources()->default_text_color); gui->draw_line(x,y+h/2,x+w,y+h/2); - hidden=1; f=1; } - if(!hideme && hidden!=0){ + if(!hideme && is_hidden()){ gui->set_color(get_resources()->get_bg_color()); gui->draw_box(x,y,w,h); show_window(); label->show_window(); - hidden=0; f=1; } @@ -1915,7 +1909,7 @@ public: BluebananaMain *plugin; BluebananaWindow *gui; BC_Title *label; - int x,y,padx,hidden; + int x,y,padx; }; // ------------------------------------------ Use mask ---------------------------------------- @@ -1931,7 +1925,6 @@ public: this->y=-1; gui->add_subwindow(this->label); gui->add_subwindow(this); - hidden = -1; } virtual int handle_event(){ plugin->config.use_mask=get_value(); @@ -1969,14 +1962,13 @@ public: case BC_YUV888: case BC_RGB161616: case BC_YUV161616: - if(hidden!=1){ + if(!is_hidden()){ hide_window(); label->hide_window(); gui->set_color(get_resources()->get_bg_color()); gui->draw_box(x,y,w,h); gui->set_color(get_resources()->default_text_color); gui->draw_line(x,y+h/2,x+w,y+h/2); - hidden=1; f=1; } break; @@ -1985,12 +1977,11 @@ public: case BC_YUVA8888: case BC_RGBA16161616: case BC_YUVA16161616: - if(hidden!=0){ + if(is_hidden()){ gui->set_color(get_resources()->get_bg_color()); gui->draw_box(x,y,w,h); show_window(); label->show_window(); - hidden=0; f=1; } break; @@ -2009,7 +2000,7 @@ public: BluebananaMain *plugin; BluebananaWindow *gui; BC_Title *label; - int x,y,padx,hidden; + int x,y,padx; }; // --------------------------------------- Main GUI window -------------------------------------- diff --git a/cinelerra-5.1/plugins/echocancel/echocancel.C b/cinelerra-5.1/plugins/echocancel/echocancel.C index 31c4dc4e..80787f40 100644 --- a/cinelerra-5.1/plugins/echocancel/echocancel.C +++ b/cinelerra-5.1/plugins/echocancel/echocancel.C @@ -743,7 +743,7 @@ static inline void cx_product(int n, int sf, double *rp, double *ip, static inline void cj_product(int n, int sf, double *rp, double *ip, double *arp, double *aip, double *brp, double *bip) { - int m = !sf ? n : n/2, i = 0; + int m = !sf ? n-1 : n/2, i = 0; while( i <= m ) { double ar = arp[i], ai = aip[i]; double br = brp[i], bi = -bip[i]; diff --git a/cinelerra-5.1/plugins/motion51/Makefile b/cinelerra-5.1/plugins/motion51/Makefile new file mode 100644 index 00000000..0990f83f --- /dev/null +++ b/cinelerra-5.1/plugins/motion51/Makefile @@ -0,0 +1,13 @@ +include ../../plugin_defs + +OBJS := \ + $(OBJDIR)/motion51.o \ + $(OBJDIR)/motionwindow51.o + +PLUGIN = motion51 + +include ../../plugin_config + +$(OBJDIR)/motion51.o: motion51.C +$(OBJDIR)/motionwindow51.o: motionwindow51.C + diff --git a/cinelerra-5.1/plugins/motion51/motion51.C b/cinelerra-5.1/plugins/motion51/motion51.C new file mode 100644 index 00000000..402a5a00 --- /dev/null +++ b/cinelerra-5.1/plugins/motion51/motion51.C @@ -0,0 +1,963 @@ +#include "affine.h" +#include "bchash.h" +#include "clip.h" +#include "filexml.h" +#include "fourier.h" +#include "keyframe.h" +#include "language.h" +#include "mainerror.h" +#include "motion51.h" +#include "motionwindow51.h" +#include "mutex.h" +#include "transportque.inc" + +#include +#include +#include + +static const double passing = 0.92; +static bool passible(double v) { return v > passing; } + + +REGISTER_PLUGIN(Motion51Main) + +void Motion51Config::init() +{ + sample_steps = 1024; + sample_r = 50.f; + block_x = block_y = 50.f; + block_w = block_h = 50.f; + horiz_limit = vert_limit = twist_limit = 50.f; + shake_fade = twist_fade = 3.f; + draw_vectors = 1; + strcpy(tracking_file, TRACKING_FILE); + tracking = 0; +} + +Motion51Config::Motion51Config() +{ + init(); +} + +int Motion51Config::equivalent(Motion51Config &that) +{ + return horiz_limit == that.horiz_limit && + vert_limit == that.vert_limit && + twist_limit == that.twist_limit && + shake_fade == that.shake_fade && + twist_fade == that.twist_fade && + sample_r == that.sample_r && + sample_steps == that.sample_steps && + draw_vectors == that.draw_vectors && + EQUIV(block_x, that.block_x) && + EQUIV(block_y, that.block_y) && + block_w == that.block_w && + block_h == that.block_h && + !strcmp(tracking_file, that.tracking_file) && + tracking == that.tracking; +} + +void Motion51Config::copy_from(Motion51Config &that) +{ + horiz_limit = that.horiz_limit; + vert_limit = that.vert_limit; + twist_limit = that.twist_limit; + shake_fade = that.shake_fade; + twist_fade = that.twist_fade; + sample_r = that.sample_r; + sample_steps = that.sample_steps; + draw_vectors = that.draw_vectors; + block_x = that.block_x; + block_y = that.block_y; + block_w = that.block_w; + block_h = that.block_h; + strcpy(tracking_file, that.tracking_file); + tracking = that.tracking; +} + +void Motion51Config::interpolate(Motion51Config &prev, Motion51Config &next, + int64_t prev_frame, int64_t next_frame, int64_t current_frame) +{ + copy_from(prev); +} + + +Motion51Main::Motion51Main(PluginServer *server) + : PluginVClient(server) +{ + out_frame = 0; out_position = -1; + ref_frame = 0; ref_position = -1; + tmp_frame = 0; + affine = 0; + motion_scan = 0; + + cache_file[0] = 0; + cache_fp = active_fp = 0; + cache_line[0] = 0; + cache_key = active_key = -1; + tracking_position = -1; + + out_w = out_h = out_r = 0; + rx = ry = rw = rh = rr = 0; + current_dx = current_dy = 0; + x_steps = y_steps = 16; + r_steps = 4; + cir_sz = 0; cir_r = 0; + xpts = ypts = 0; + total_dx = total_dy = 0; + total_angle = 0; +} + +Motion51Main::~Motion51Main() +{ + update_cache_file(); + delete out_frame; + delete ref_frame; + delete tmp_frame; + delete affine; + delete motion_scan; + delete [] xpts; + delete [] ypts; +} + +const char* Motion51Main::plugin_title() { return _("Motion51"); } +int Motion51Main::is_realtime() { return 1; } +int Motion51Main::is_multichannel() { return 1; } + + +NEW_WINDOW_MACRO(Motion51Main, Motion51Window) +LOAD_CONFIGURATION_MACRO(Motion51Main, Motion51Config) + + +void Motion51Main::update_gui() +{ + if( !thread ) return; + if( !load_configuration() ) return; + thread->window->lock_window("Motion51Main::update_gui"); + Motion51Window *window = (Motion51Window*)thread->window; + window->update_gui(); + thread->window->unlock_window(); +} + + + + +void Motion51Main::save_data(KeyFrame *keyframe) +{ + FileXML output; + +// cause data to be stored directly in text + output.set_shared_output(keyframe->get_data(), MESSAGESIZE); + output.tag.set_title("MOTION51"); + output.tag.set_property("HORIZ_LIMIT", config.horiz_limit); + output.tag.set_property("VERT_LIMIT", config.vert_limit); + output.tag.set_property("TWIST_LIMIT", config.twist_limit); + output.tag.set_property("SHAKE_FADE", config.shake_fade); + output.tag.set_property("TWIST_FADE", config.twist_fade); + output.tag.set_property("SAMPLE_R", config.sample_r); + output.tag.set_property("SAMPLE_STEPS", config.sample_steps); + output.tag.set_property("DRAW_VECTORS", config.draw_vectors); + output.tag.set_property("BLOCK_W", config.block_w); + output.tag.set_property("BLOCK_H", config.block_h); + output.tag.set_property("BLOCK_X", config.block_x); + output.tag.set_property("BLOCK_Y", config.block_y); + output.tag.set_property("TRACKING_FILE", config.tracking_file); + output.tag.set_property("TRACKING", config.tracking); + output.append_tag(); + output.tag.set_title("/MOTION51"); + output.append_tag(); + output.terminate_string(); +} + +void Motion51Main::read_data(KeyFrame *keyframe) +{ + FileXML input; + input.set_shared_input(keyframe->get_data(), strlen(keyframe->get_data())); + int result = 0; + + while( !(result = input.read_tag()) ) { + if( input.tag.title_is("MOTION51") ) { + config.horiz_limit = input.tag.get_property("HORIZ_LIMIT", config.horiz_limit); + config.vert_limit = input.tag.get_property("VERT_LIMIT", config.vert_limit); + config.twist_limit = input.tag.get_property("TWIST_LIMIT", config.twist_limit); + config.shake_fade = input.tag.get_property("SHAKE_FADE", config.shake_fade); + config.twist_fade = input.tag.get_property("TWIST_FADE", config.twist_fade); + config.sample_r = input.tag.get_property("SAMPLE_R", config.sample_r); + config.sample_steps = input.tag.get_property("SAMPLE_STEPS", config.sample_steps); + config.draw_vectors = input.tag.get_property("DRAW_VECTORS", config.draw_vectors); + config.block_w = input.tag.get_property("BLOCK_W", config.block_w); + config.block_h = input.tag.get_property("BLOCK_H", config.block_h); + config.block_x = input.tag.get_property("BLOCK_X", config.block_x); + config.block_y = input.tag.get_property("BLOCK_Y", config.block_y); + input.tag.get_property("TRACKING_FILE", config.tracking_file); + config.tracking = input.tag.get_property("TRACKING", config.tracking); + } + } +} + +#if 0 +static void snap(const char *fn, VFrame *img, float x, float y, float r) +{ + VFrame vfrm(img->get_w(),img->get_h(),img->get_color_model()); + vfrm.copy_from(img); + vfrm.draw_smooth(x-r,y, x-r,y+r, x,y+r); + vfrm.draw_smooth(x,y+r, x+r,y+r, x+r,y); + vfrm.draw_smooth(x+r,y, x+r,y-r, x,y-r); + vfrm.draw_smooth(x,y-r, x-r,y-r, x-r,y); + vfrm.write_png(fn); +} +#endif + +#if 0 +// nearest sample +static void nearest_uint8(double *pix[3], double rx, double ry, + uint8_t **rows, int psz, int iw1, int ih1) +{ + int ix = (int)rx, iy = (int)ry; + bclamp(ix, 0, iw1); bclamp(iy, 0, ih1); + uint8_t *cp = (uint8_t*)(rows[iy] + psz * ix); + for( int i=0; i<3; ++pix[i], ++cp, ++i ) *pix[i] = *cp; +} +static void nearest_float(double *pix[3], double rx, double ry, + uint8_t **rows, int psz, int iw1, int ih1) +{ + int ix = (int)rx, iy = (int)ry; + bclamp(ix, 0, iw1); bclamp(iy, 0, ih1); + float *fp = (float*)(rows[iy] + psz * ix); + for( int i=0; i<3; ++pix[i], ++fp, ++i ) *pix[i] = *fp; +} +#endif + +// corner interpolation sample +static void corner_uint8(double *pix[3], double rx, double ry, + uint8_t **rows, int psz, int iw1, int ih1) +{ + bclamp(rx, 0, iw1); bclamp(ry, 0, ih1); + int iy = (int)ry; + double yf1 = ry - iy, yf0 = 1.0 - yf1; + uint8_t *row0 = rows[iy]; + if( iy < ih1 ) ++iy; + uint8_t *row1 = rows[iy]; + int ix = (int)rx; + double xf1 = rx - ix, xf0 = 1.0 - xf1; + int i0 = psz * ix; + if( ix < iw1 ) ++ix; + int i1 = psz * ix; + uint8_t *cp00 = (uint8_t*)&row0[i0], *cp01 = (uint8_t*)&row0[i1]; + uint8_t *cp10 = (uint8_t*)&row1[i0], *cp11 = (uint8_t*)&row1[i1]; + double a00 = xf0 * yf0, a01 = xf1 * yf0; + double a10 = xf0 * yf1, a11 = xf1 * yf1; + for( int i=0; i<3; ++pix[i], ++cp00, ++cp01, ++cp10, ++cp11, ++i ) + *pix[i] = *cp00*a00 + *cp01*a01 + *cp10*a10 + *cp11*a11; +} +static void corner_float(double *pix[3], double rx, double ry, + uint8_t **rows, int psz, int iw1, int ih1) +{ + bclamp(rx, 0, iw1); bclamp(ry, 0, ih1); + int iy = (int)ry; + double yf1 = ry - iy, yf0 = 1.0 - yf1; + uint8_t *row0 = rows[iy]; + if( iy < ih1 ) ++iy; + uint8_t *row1 = rows[iy]; + int ix = (int)rx; + double xf1 = rx - ix, xf0 = 1.0 - xf1; + int i0 = psz * ix; + if( ix < iw1 ) ++ix; + int i1 = psz * ix; + float *fp00 = (float*)&row0[i0], *fp01 = (float*)&row0[i1]; + float *fp10 = (float*)&row1[i0], *fp11 = (float*)&row1[i1]; + double a00 = xf0 * yf0, a01 = xf1 * yf0; + double a10 = xf0 * yf1, a11 = xf1 * yf1; + for( int i=0; i<3; ++pix[i], ++fp00, ++fp01, ++fp10, ++fp11, ++i ) + *pix[i] = *fp00*a00 + *fp01*a01 + *fp10*a10 + *fp11*a11; +} + + +static inline double cor(int n, double *ap, double *bp) +{ + double s = 0; + while( --n >= 0 ) s += *ap++ * *bp++; + return s; +} + +static inline double sqr(double v) { return v*v; } + +static inline void cj_product(int n, int sf, double *rp, double *ip, + double *arp, double *aip, double *brp, double *bip) +{ + int m = !sf ? n-1 : n/2, i = 0; + while( i <= m ) { + double ar = arp[i], ai = aip[i]; + double br = brp[i], bi = -bip[i]; + rp[i] = ar*br - ai*bi; // complex a*ib' + ip[i] = ar*bi + ai*br; + ++i; + } + if( !sf ) return; + while( --m > 0 ) { rp[i] = rp[m]; ip[i] = -ip[m]; ++i; } +} + +typedef struct { double x, y, d; } coord_t; +static int coord_cmpr(const void *ap, const void *bp) +{ + coord_t *a = (coord_t *)ap, *b = (coord_t *)bp; + return a->d == b->d ? 0 : a->d < b->d ? -1 : 1; +} + + +int64_t Motion51Main::get_ref_position() +{ + int64_t position = out_position - 1; + if( position < 0 || position != ref_position ) { +// clip to edit boundaries + int64_t pos = get_source_start(); + if( position < pos ) + position = pos; + else if( position >= (pos += get_total_len()) ) + position = pos-1; +// clip to keyframe boundaries + KeyFrame *next_keyframe = get_next_keyframe(out_position, 1); + int64_t keyframe_end = next_keyframe->position; + if( (pos=next_keyframe->position) > 0 && position > keyframe_end ) + position = keyframe_end; + KeyFrame *prev_keyframe = get_prev_keyframe(out_position, 1); + int64_t keyframe_start = prev_keyframe->position; + if( keyframe_start > 0 && position < keyframe_start ) + position = keyframe_start; + } + return position; +} + +void Motion51Main::set_tracking_path() +{ + const char *sp = TRACKING_FILE; + char *cp = config.tracking_file, *ep = cp+sizeof(config.tracking_file)-1; + while( cp < ep && *sp != 0 ) *cp++ = *sp++; + if( cp < ep && (sp=get_source_path()) ) { + *cp++ = '-'; + const char *bp = strrchr(sp,'/'); + if( bp ) sp = bp+1; + while( cp < ep && *sp != 0 ) { + *cp++ = (*sp>='a' && *sp<='z') || + (*sp>='A' && *sp<='Z') || + (*sp>='0' && *sp<='9') ? *sp : '_'; + ++sp; + } + } + *cp = 0; +} + +void Motion51Main::update_tracking_cache() +{ + if( (!config.tracking && cache_fp) || (config.tracking && !cache_fp) || + (active_fp && active_key > get_source_position()) ) + update_cache_file(); +} + +int Motion51Main::load_tracking_cache(int64_t position) +{ + if( !config.tracking ) return 1; + if( get_cache_line(position) ) return 1; + if( sscanf(cache_line, "%jd %f %f %f", &position, &dx, &dy, &dt) != 4 ) return 1; + return 0; +} + +void Motion51Main::save_tracking_cache(int64_t position) +{ + if( !config.tracking ) return; + char line[BCSTRLEN]; + snprintf(line, sizeof(line), "%jd %f %f %f\n", position, dx, dy, dt); + put_cache_line(line); +} + +void Motion51Main::match(VFrame *ref, VFrame *cur) +{ + if( !motion_scan ) { + int cpus = get_project_smp()+1; + motion_scan = new Motion51Scan(this, cpus, x_steps, y_steps); + } + + motion_scan->scan(ref, cur, config.sample_steps); + + if( passible(motion_scan->cor_value) ) { + dx = motion_scan->dx_result - rx; + dy = motion_scan->dy_result - ry; + dt = motion_scan->dt_result * 180./M_PI; + } + else { + total_dx = total_dy = total_angle = 0; + dx = dy = dt = 0; + } + +} + +int Motion51Main::transform_target(int use_opengl) +{ + if( dx || dy || dt ) { + int cpus = get_project_smp()+1; + if( !affine ) affine = new AffineEngine(cpus, cpus); + affine->set_in_pivot(rx, ry); + affine->set_out_pivot(rx-dx, ry-dy); + if( use_opengl ) + return run_opengl(); + new_temp(out_frame, out)->copy_from(out); + out->clear_frame(); + affine->rotate(out, out_frame, dt); +//printf("transform_target at %jd: rotate(%f, %f, %f)\n", out_position, dx, dy, dt); + } + return 0; +} + +int Motion51Main::handle_opengl() +{ +#ifdef HAVE_GL + affine->set_opengl(1); + affine->rotate(out, out, dt); + out->screen_to_ram(); + affine->set_opengl(0); +#endif + return 0; +} + + +int Motion51Main::process_buffer(VFrame **frame, int64_t position, double frame_rate) +{ + int need_reconfigure = load_configuration(); + + int target_layer = 0; + int reference_layer = PluginClient::total_in_buffers-1; + VFrame *ref_layer = frame[reference_layer]; + out = frame[target_layer]; + out_position = position; + if( !out_position ) total_dx = total_dy = total_angle = 0; + get_pixel = BC_CModels::is_float(out->get_color_model()) ? + &corner_float : &corner_uint8; + + int use_opengl = get_use_opengl(); + read_frame(out, target_layer, out_position, frame_rate, use_opengl); + out_w = out->get_w(); + out_h = out->get_h(); + out_r = 0.5 * (out_w < out_h ? out_w : out_h); + rw = out_w * config.block_w/100.; + rh = out_h * config.block_h/100.; + rx = out_w * config.block_x/100.; + ry = out_h * config.block_y/100.; + rr = out_r * config.sample_r/100.; + reset_sample(config.sample_steps, rr); + dx = 0; dy = 0; dt = 0; + + update_tracking_cache(); + if( load_tracking_cache(out_position) ) { + int64_t ref_pos = get_ref_position(); + if( !ref_frame || ref_pos != ref_position || need_reconfigure ) { + new_temp(ref_frame, ref_layer); + read_frame(ref_frame, reference_layer, ref_pos, frame_rate, 0); + total_dx = total_dy = 0; total_angle = 0; + ref_position = ref_pos; + } + VFrame *cur_frame = out; + if( reference_layer != target_layer ) { + new_temp(tmp_frame, ref_layer); + read_frame(tmp_frame, reference_layer, out_position, frame_rate, 0); + cur_frame = tmp_frame; + } + match(ref_frame, cur_frame); + save_tracking_cache(out_position); + } + current_dx = dx; current_dy = dy; + double sf = 1. - config.shake_fade/100.; + dx += total_dx * sf; dy += total_dy * sf; + double rf = 1. - config.twist_fade/100.; + dt += total_angle * rf; + if( dt < -180. ) dt += 360.; + else if( dt > 180. ) dt -= 360.; + + float tot_dx = out_w * config.horiz_limit/100.; + bclamp(dx, -tot_dx, tot_dx); + float tot_dy = out_h * config.vert_limit/100.; + bclamp(dy, -tot_dy, tot_dy); + float tot_dt = 180. * config.twist_limit/100.; + bclamp(dt, -tot_dt, +tot_dt); + total_dx = dx; total_dy = dy; total_angle = dt; + if( ref_frame && reference_layer == target_layer && + ref_position+1 == out_position && + ref_frame->get_w() == out->get_w() && + ref_frame->get_h() == out->get_h() && + ref_frame->get_color_model() == out->get_color_model() ) { + ref_frame->copy_from(out); + ref_position = out_position; + } + transform_target(use_opengl); + + if( config.draw_vectors ) + draw_vectors(out); + + return 0; +} + +VFrame* Motion51Main::new_temp(VFrame *&tmp, VFrame *ref) +{ + if( tmp && (tmp->get_w() != ref->get_w() || tmp->get_h() != ref->get_h() || + tmp->get_color_model() != ref->get_color_model()) ) { + delete tmp; tmp = 0; + } + if( !tmp ) + tmp = new VFrame(0, -1, ref->get_w(), ref->get_h(), ref->get_color_model(), -1); + return tmp; +} + +void Motion51Main::reset_sample(int sz, double r) +{ + if( cir_sz == sz && cir_r == r ) return; + if( cir_sz != sz ) { + cir_sz = sz; + delete xpts; xpts = new double[cir_sz]; + delete ypts; ypts = new double[cir_sz]; + } + cir_r = r; + int n = cir_sz / r_steps; + double dt = (2*M_PI)/n; + double dr = r / r_steps; + for( int it=0; itget_w(), iw1 = iw-1; + int ih = img->get_h(), ih1 = ih-1; + uint8_t **rows = img->get_rows(); + int psz = BC_CModels::calculate_pixelsize(img->get_color_model()); + + double *xp = xpts, *yp = ypts; + for( int i=cir_sz; --i>=0; ++xp,++yp ) { + double px = x + *xp, py = y + *yp; + get_pixel(pix, px, py, rows, psz, iw1, ih1); + } +} + +void Motion51Main::centroid(double *pix[3], double *ctr_v, double *ctr_x, double *ctr_y) +{ + for( int i=0; i<3; ++i ) + ctr_v[i] = ctr_x[i] = ctr_y[i] = 0; + double *xp = xpts, *yp = ypts; + for( int k=cir_sz; --k>=0; ++xp,++yp ) { + double x = rx + *xp, y = ry + *yp; + for( int i=0; i<3; ++pix[i],++i ) { + double v = *pix[i]; + ctr_v[i] += v; + ctr_x[i] += v * x; + ctr_y[i] += v * y; + } + } + for( int i=0; i<3; ++i ) { + if( !ctr_v[i] ) continue; + ctr_x[i] /= ctr_v[i]; + ctr_y[i] /= ctr_v[i]; + ctr_v[i] /= cir_sz; + } +} + + +void Motion51Main::draw_vectors(VFrame *img) +{ + img->draw_arrow(rx, ry, rx+current_dx, ry+current_dy); + +// img->draw_smooth(rx-rr,ry, rx-rr,ry+rr, rx,ry+rr); +// img->draw_smooth(rx,ry+rr, rx+rr,ry+rr, rx+rr,ry); +// img->draw_smooth(rx+rr,ry, rx+rr,ry-rr, rx,ry-rr); +// img->draw_smooth(rx,ry-rr, rx-rr,ry-rr, rx-rr,ry); + + float rx1 = rx - 0.5*rw; + float ry1 = ry - 0.5*rh; + float rx2 = rx1 + rw; + float ry2 = ry1 + rh; + + img->draw_line(rx1, ry1, rx2, ry1); + img->draw_line(rx2, ry1, rx2, ry2); + img->draw_line(rx2, ry2, rx1, ry2); + img->draw_line(rx1, ry2, rx1, ry1); + + float sx1 = rx1 - rr, sy1 = ry1 - rr; + float sx2 = rx2 + rr, sy2 = ry2 + rr; + + img->draw_smooth(sx1, ry1, sx1, sy1, rx1, sy1); + img->draw_line(rx1, sy1, rx2, sy1); + img->draw_smooth(rx2, sy1, sx2, sy1, sx2, ry1); + img->draw_line(sx2, ry1, sx2, ry2); + img->draw_smooth(sx2, ry2, sx2, sy2, rx2, sy2); + img->draw_line(rx2, sy2, rx1, sy2); + img->draw_smooth(rx1, sy2, sx1, sy2, sx1, ry2); + img->draw_line(sx1, ry2, sx1, ry1); + + double *xp = xpts, *yp = ypts; + for( int i=cir_sz; --i>=0; ++xp, ++yp ) + img->draw_pixel(rx+*xp, ry+*yp); +} + +int Motion51Main::open_cache_file() +{ + if( cache_fp ) return 0; + if( !cache_file[0] ) return 1; + if( !(cache_fp = fopen(cache_file, "r")) ) return 1; +// match timestamp, asset path + char line[BCTEXTLEN], *cp = line, *ep = cp+sizeof(line); + if( fgets(line,sizeof(line),cache_fp) ) { + int64_t tm = strtoul(cp,&cp,0); +// time 0 matches everything + if( !tm ) return 0; + const char *sp = get_source_path(); + if( !sp ) return 0; + if( cp < ep && *cp == ' ' ) ++cp; + int n = strlen(cp); + if( n > 0 && cp[n-1] == '\n' ) cp[n-1] = 0; + struct stat st; + if( !strcmp(cp, sp) && !stat(cp,&st) && st.st_mtime == tm ) + return 0; + } + fclose(cache_fp); cache_fp = 0; + return 1; +} + +void Motion51Main::close_cache_file() +{ + if( !cache_fp ) return; + fclose(cache_fp); + cache_fp = 0; cache_key = -1; + tracking_position = -1; +} + +int Motion51Main::load_cache_line() +{ + cache_key = -1; + if( open_cache_file() ) return 1; + if( !fgets(cache_line, sizeof(cache_line), cache_fp) ) return 1; + cache_key = strtol(cache_line, 0, 0); + return 0; +} + +int Motion51Main::get_cache_line(int64_t key) +{ + if( cache_key == key ) return 0; + if( open_cache_file() ) return 1; + if( cache_key >= 0 && key > cache_key ) { + if( load_cache_line() ) return 1; + if( cache_key == key ) return 0; + if( cache_key > key ) return 1; + } +// binary search file + fseek(cache_fp, 0, SEEK_END); + int64_t l = -1, r = ftell(cache_fp); + while( (r - l) > 1 ) { + int64_t m = (l + r) / 2; + fseek(cache_fp, m, SEEK_SET); +// skip to start of next line + if( !fgets(cache_line, sizeof(cache_line), cache_fp) ) + return -1; + if( !load_cache_line() ) { + if( cache_key == key ) + return 0; + if( cache_key < key ) { l = m; continue; } + } + r = m; + } + return 1; +} + +int Motion51Main::locate_cache_line(int64_t key) +{ + int ret = 1; + if( key < 0 || !(ret=get_cache_line(key)) || + ( cache_key >= 0 && cache_key < key ) ) + ret = load_cache_line(); + return ret; +} + +int Motion51Main::put_cache_line(const char *line) +{ + int64_t key = strtol(line, 0, 0); + if( key == active_key ) return 1; + if( !active_fp ) { + close_cache_file(); + sprintf(cache_file, "%s.bak", config.tracking_file); + ::rename(config.tracking_file, cache_file); + if( !(active_fp = fopen(config.tracking_file, "w")) ) { + perror(config.tracking_file); + fprintf(stderr, "err writing key %jd\n", key); + return -1; + } + const char *sp = get_source_path(); + int64_t tm = 0; + if( sp ) { + struct stat st; + if( !stat(sp,&st) ) tm = st.st_mtime; + } + fprintf(active_fp, "%jd %s\n",tm, sp); + active_key = -1; + } + + if( active_key < key ) { + locate_cache_line(active_key); + while( cache_key >= 0 && key >= cache_key ) { + if( key > cache_key ) + fputs(cache_line, active_fp); + load_cache_line(); + } + } + + active_key = key; + fputs(line, active_fp); + fflush(active_fp); + return 0; +} + +void Motion51Main::update_cache_file() +{ + if( active_fp ) { + locate_cache_line(active_key); + while( cache_key >= 0 ) { + fputs(cache_line, active_fp); + load_cache_line(); + } + close_cache_file(); + ::remove(cache_file); + fclose(active_fp); active_fp = 0; + active_key = -1; + } + else + close_cache_file(); + strcpy(cache_file, config.tracking_file); +} + +Motion51Scan::Motion51Scan(Motion51Main *plugin, int n_thds, int x_steps, int y_steps) + : LoadServer(n_thds, x_steps*y_steps) +{ + this->plugin = plugin; + this->x_steps = x_steps; + this->y_steps = y_steps; + this->fft = new FFT; + this->result_lock = new Mutex("Motion51Scan::result_lock"); + + cur = ref = 0; + bx = by = br = bw = bh = 0; + rpix_sz = 0; + for( int i=0; i<3; ++i ) { + rpix[i] = 0; rpwr[i] = 0; + rctr_v[i] = rctr_x[i] = rctr_y[i] = 0; + ref_real[i] = ref_imag[i] = 0; + } + cor_value = value = 0; + dx_result = dy_result = 0; + dt_result = 0; +} + + +Motion51Scan::~Motion51Scan() +{ + for( int i=0; i<3; ++i ) { + delete [] rpix[i]; + delete [] ref_real[i]; + delete [] ref_imag[i]; + } + delete fft; + delete result_lock; +} + +// sum absolute diff of ref at (rx,ry) - (cur(cx,cy) rotated ct) +// downsampled using corner_sample to x_steps, y_steps +double Motion51Scan::compare(double cx, double cy, double ct) +{ + int iw = ref->get_w(), iw1 = iw-1; + int ih = ref->get_h(), ih1 = ih-1; + int xsz = x_steps;// iw; + int ysz = y_steps;// ih; + int psz = BC_CModels::calculate_pixelsize(cur->get_color_model()); + uint8_t **ref_rows = ref->get_rows(); + uint8_t **cur_rows = cur->get_rows(); + double cos_ct = cos(ct), sin_ct = sin(ct); + double rx = plugin->rx, ry = plugin->ry; + double sx = (double)iw/xsz, sy = (double)ih/ysz; + double cpix[3][xsz], rpix[3][xsz]; + double v = 0; + for( int iy=0; iyget_pixel(ref_pix, x, y, ref_rows, psz, iw1, ih1); + double tx = x-rx, ty = y-ry; + double xt = cos_ct*tx - sin_ct*ty + cx; + double yt = cos_ct*ty + sin_ct*tx + cy; + plugin->get_pixel(cur_pix, xt, yt, cur_rows, psz, iw1, ih1); + } + for( int i=0; i<3; ++i ) { + double *rp = rpix[i], *cp = cpix[i]; + for( int k=x_steps; --k>=0; ++rp,++cp ) v += fabs(*rp - *cp); + } + } + double mx = BC_CModels::calculate_max(ref->get_color_model()); + v = 1.-v/(3*mx * x_steps*y_steps); + return v; +} + +void Motion51Scan::scan(VFrame *ref, VFrame *cur, int sz) +{ + this->ref = ref; + this->cur = cur; + if( this->rpix_sz != sz ) { + this->rpix_sz = sz; + for( int i=0; i<3; ++i ) { + delete [] rpix[i]; rpix[i] = new double[sz]; + delete [] ref_real[i]; ref_real[i] = new double[sz]; + delete [] ref_imag[i]; ref_imag[i] = new double[sz]; + } + } + + bw = plugin->rw; bh = plugin->rh; + bx = plugin->rx; by = plugin->ry; + br = plugin->rr; + + double *ref_pix[3] = { rpix[0], rpix[1], rpix[2] }; + plugin->get_samples(ref, ref_pix, bx, by); + double *pix[3] = { rpix[0], rpix[1], rpix[2] }; + plugin->centroid(&pix[0], &rctr_v[0], &rctr_x[0], &rctr_y[0]); + for( int i=0; i<3; ++i ) { + fft->do_fft(sz, 0, rpix[i], 0, ref_real[i], ref_imag[i]); + rpwr[i] = cor(sz, rpix[i], rpix[i]); + } + double scan_limit = 0.25; // quarter pixel resolution +//printf("frame: %jd\n", plugin->get_source_position()); + while( bw/x_steps > scan_limit || bh/y_steps > scan_limit ) { + dx_result = dy_result = dt_result = 0; + cor_value = value = 0; +//printf(" bx,by %6.2f,%-6.2f bw,bh %6.2f,%-6.2f ",bx,by, bw,bh); + process_packages(); + bx = dx_result; + by = dy_result; +//printf(" r = %f(%f), %6.2f,%-6.2f\n",value,dt_result*180/M_PI,bx,by); + bw *= 0.5; bh *= 0.5; + } +} + +void Motion51Scan::init_packages() +{ +// sort in increasing distance from current displacement + double tx = plugin->rx + plugin->total_dx; + double ty = plugin->ry + plugin->total_dy; + int npkgs = get_total_packages(); + coord_t coords[npkgs]; + int i = 0; + double x0 = bx - bw/2, y0 = by - bh/2; + for( int iy=0; iyx = x; cp->y = y; cp->d = d; + } + } + qsort(&coords,npkgs,sizeof(coords[0]),coord_cmpr); + + for( i=0; ix = cp->x; pkg->y = cp->y; + } +} + +Motion51ScanUnit::Motion51ScanUnit(Motion51Scan *server, Motion51Main *plugin) + : LoadClient(server) +{ + this->server = server; + this->plugin = plugin; + fft = new FFT; +} +Motion51ScanUnit::~Motion51ScanUnit() +{ + delete fft; +} + +void Motion51ScanUnit::process_package(LoadPackage *package) +{ + Motion51ScanPackage *pkg = (Motion51ScanPackage *)package; + int sz = server->rpix_sz; + double cur_real[3][sz], cur_imag[3][sz]; + double cpwr[3], cpix[3][sz], *cur_pix[3] = { cpix[0], cpix[1], cpix[2] }; + plugin->get_samples(server->cur, cur_pix, pkg->x, pkg->y); + + double *pix[3] = { cpix[0], cpix[1], cpix[2] }; + double cctr_v[3], cctr_x[3], cctr_y[3]; + plugin->centroid(&pix[0], &cctr_v[0], &cctr_x[0], &cctr_y[0]); + double mx = BC_CModels::calculate_max(server->ref->get_color_model()); + for( int i=0; i<3; ++i ) { + double v = 1. - fabs(server->rctr_v[i]-cctr_v[i]) / mx; + if( !passible(v) ) return; + double *rctr_x = server->rctr_x, *rctr_y = server->rctr_y; + double d = sqrt(sqr(rctr_x[i]-cctr_x[i]) + sqr(rctr_y[i]-cctr_y[i])); + v = 1 - d / plugin->cir_r; + if( !passible(v) ) return; + } + for( int i=0; i<3; ++i ) { + double cs = cor(sz, cpix[i], cpix[i]); + double rs = server->rpwr[i]; + double ss = rs + cs; + if( ss == 0 ) ss = 1; + double v = 1. - fabs(rs - cs) / ss; + if( ! passible(v) ) return; + cpwr[i] = 1. / ss; + } + + double cor_real[3][sz], cor_imag[3][sz]; + for( int i=0; i<3; ++i ) { + fft->do_fft(sz, 0, cpix[i], 0, cur_real[i], cur_imag[i]); + cj_product(sz, 0, cur_real[i], cur_imag[i], + server->ref_real[i], server->ref_imag[i], + cur_real[i], cur_imag[i]); + fft->do_fft(sz, 1, cur_real[i], cur_imag[i], cor_real[i], cor_imag[i]); + } + double sv = 0; + int st = 0; + for( int t=0; t= v ) continue; + sv = v; st = t; + } + if( server->cor_value > sv ) return; + server->cor_value = sv; + if( st > sz/2 ) st -= sz; + int steps = plugin->r_steps; + double tt = steps*(2*M_PI); + double th = st*tt/sz, dt = th; + double value = 0; + double dth = (2*M_PI)/sz; + for( int i=-steps; i<=steps; ++i ) { + double t = th + i*dth; + double v = server->compare(pkg->x, pkg->y, -t); + if( value >= v ) continue; + value = v; dt = t; + } +//static int dbg = 0; +//if( dbg ) +//printf(" %d. %.3f,%.3f %f = %f / %f + %f\n", +// package_number,pkg->x,pkg->y,dt*180./M_PI,value,sv); + server->result_lock->lock("Motion51Scan::process_package"); + if( value > server->value ) { + server->value = value; + server->dt_result = dt; + server->dx_result = pkg->x; + server->dy_result = pkg->y; + } + server->result_lock->unlock(); +} + diff --git a/cinelerra-5.1/plugins/motion51/motion51.h b/cinelerra-5.1/plugins/motion51/motion51.h new file mode 100644 index 00000000..bb93ef15 --- /dev/null +++ b/cinelerra-5.1/plugins/motion51/motion51.h @@ -0,0 +1,181 @@ + +/* + * CINELERRA + * Copyright (C) 2008 Adam Williams + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef __MOTION_51_H__ +#define __MOTION_51_H__ + +#include +#include +#include + +#include "affine.inc" +#include "bchash.inc" +#include "filexml.inc" +#include "keyframe.inc" +#include "loadbalance.h" +#include "motion51.inc" +#include "overlayframe.inc" +#include "pluginvclient.h" +#include "vframe.inc" + +class FFT; +class Motion51Config; +class Motion51Main; +class Motion51ScanUnit; +class Motion51ScanPackage; +class Motion51Scan; + +class Motion51Config +{ +public: + Motion51Config(); + + void init(); + int equivalent(Motion51Config &that); + void copy_from(Motion51Config &that); + void interpolate(Motion51Config &prev, Motion51Config &next, + int64_t prev_frame, int64_t next_frame, int64_t current_frame); + + float block_x, block_y, block_w, block_h; + float sample_r; + int sample_steps; + float horiz_limit, vert_limit, twist_limit; + float shake_fade, twist_fade; + int draw_vectors; + + char tracking_file[BCTEXTLEN]; + int tracking; +}; + + +class Motion51Main : public PluginVClient +{ +public: + Motion51Main(PluginServer *server); + ~Motion51Main(); + + int process_buffer(VFrame **frame, int64_t position, double frame_rate); + int is_multichannel(); + int is_realtime(); + void save_data(KeyFrame *keyframe); + void read_data(KeyFrame *keyframe); + void update_gui(); + void reset_sample(int sz, double r); + void get_samples(VFrame *img, double *pix[3], double x, double y); + void centroid(double *pix[3], double *ctr_v, double *ctr_x, double *ctr_y); + void draw_vectors(VFrame *img); + + int64_t get_ref_position(); + void update_tracking_cache(); + int load_tracking_cache(int64_t position); + void save_tracking_cache(int64_t position); + + void match(VFrame *ref, VFrame *cur); + int transform_target(int use_opengl); + int handle_opengl(); + VFrame* new_temp(VFrame *&tmp, VFrame *ref); + + PLUGIN_CLASS_MEMBERS2(Motion51Config) + + char cache_file[BCTEXTLEN]; + FILE *cache_fp, *active_fp; + void set_tracking_path(); + int open_cache_file(); + void close_cache_file(); + int load_cache_line(); + int locate_cache_line(int64_t key); + int get_cache_line(int64_t key); + int put_cache_line(const char *line); + void update_cache_file(); + void (*get_pixel)(double *pix[3], double rx, double ry, + uint8_t **rows, int psz, int iw1, int ih1); + + VFrame *out; + VFrame *out_frame; + int64_t out_position; + VFrame *ref_frame; + int64_t ref_position; + VFrame *tmp_frame; + AffineEngine *affine; + Motion51Scan *motion_scan; + + char cache_line[BCSTRLEN]; + int64_t cache_key, active_key; + int64_t tracking_position; + float out_w, out_h, out_r; + float dx, dy, dt; + double rx, ry, rr, rw, rh; + float current_dx, current_dy; + int x_steps, y_steps, r_steps; + double cir_r; int cir_sz; + double *xpts, *ypts; + float total_dx, total_dy; + float total_angle; +}; + +class Motion51ScanPackage : public LoadPackage +{ +public: + Motion51ScanPackage() {} + ~Motion51ScanPackage() {} + double x, y; +}; +class Motion51ScanUnit : public LoadClient +{ +public: + Motion51ScanUnit(Motion51Scan *server, Motion51Main *plugin); + ~Motion51ScanUnit(); + void process_package(LoadPackage *package); + + Motion51Scan *server; + Motion51Main *plugin; + FFT *fft; +}; + +class Motion51Scan : public LoadServer +{ +public: + Motion51Scan(Motion51Main *plugin, int total_clients, int x_steps, int y_steps); + ~Motion51Scan(); + friend class Motion51ScanUnit; + + Motion51Main *plugin; + Mutex *result_lock; + FFT *fft; + VFrame *cur, *ref; + + int x_steps, y_steps; + double bx, by, br, bw, bh; + int rpix_sz; + double *rpix[3], rpwr[3]; + double rctr_v[3], rctr_x[3], rctr_y[3]; + double *ref_real[3], *ref_imag[3]; + double cor_value, value; + double dx_result, dy_result, dt_result; + + void init_packages(); + LoadClient *new_client() { return new Motion51ScanUnit(this, plugin); } + LoadPackage *new_package() { return new Motion51ScanPackage(); } + void scan(VFrame *ref, VFrame *cur, int sz); + double compare(double cx, double cy, double ct); +}; + +#endif diff --git a/cinelerra-5.1/plugins/motion51/motion51.inc b/cinelerra-5.1/plugins/motion51/motion51.inc new file mode 100644 index 00000000..da4794ed --- /dev/null +++ b/cinelerra-5.1/plugins/motion51/motion51.inc @@ -0,0 +1,21 @@ +#ifndef __MOTION_51_INC__ +#define __MOTION_51_INC__ + +class Motion51Config; +class Motion51Main; +class Motion51ScanPackage; +class Motion51ScanUnit; +class Motion51Scan; + +class Motion51SampleSteps; +class Motion51DrawVectors; +class Motion51TrackingFile; +class Motion51Limits; +class Motion51ResetConfig; +class Motion51ResetTracking; +class Motion51EnableTracking; +class Motion51Window; + +#define TRACKING_FILE "/tmp/motion51" + +#endif diff --git a/cinelerra-5.1/plugins/motion51/motionwindow51.C b/cinelerra-5.1/plugins/motion51/motionwindow51.C new file mode 100644 index 00000000..b8aa73c0 --- /dev/null +++ b/cinelerra-5.1/plugins/motion51/motionwindow51.C @@ -0,0 +1,282 @@ + +/* + * CINELERRA + * Copyright (C) 2012 Adam Williams + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "bcdisplayinfo.h" +#include "clip.h" +#include "cstrdup.h" +#include "edl.h" +#include "fonts.h" +#include "edlsession.h" +#include "language.h" +#include "motion51.h" +#include "motionwindow51.h" +#include "mwindow.h" +#include "pluginserver.h" + +Motion51Window::Motion51Window(Motion51Main *plugin) + : PluginClientWindow(plugin, 600, 400, 600, 400, 0) +{ + this->plugin = plugin; +} + +Motion51Window::~Motion51Window() +{ +} + +void Motion51Window::create_objects() +{ + int x = 10, y = 20; + int x0 = x, x1 = get_w()/2; + add_subwindow(sample_steps = new Motion51SampleSteps(plugin, x0=x, y, 72)); + BC_Title *title = new BC_Title(x0+=sample_steps->get_w()+10, y, _("Samples")); + add_subwindow(title); + sample_steps->create_objects(); + sample_r = new Motion51Limits(plugin, this, x1,y, _("Sample Radius%"), + &plugin->config.sample_r, 0.f,100.f, 72); + sample_r->create_objects(); + y += sample_r->get_h() + 20; + + block_x = new Motion51Limits(plugin, this, x0=x,y, _("Center X%"), + &plugin->config.block_x, 0.f, 100.f, 72); + block_x->create_objects(); + block_y = new Motion51Limits(plugin, this, x1,y, _("Center Y%"), + &plugin->config.block_y, 0.f, 100.f, 72); + block_y->create_objects(); + y += block_x->get_h() + 10; + block_w = new Motion51Limits(plugin, this, x0,y, _("Search W%"), + &plugin->config.block_w, 0.f,100.f, 72); + block_w->create_objects(); + block_h = new Motion51Limits(plugin, this, x1,y, _("Search H%"), + &plugin->config.block_h, 0.f,100.f, 72); + block_h->create_objects(); + y += block_w->get_h() + 10; + + horiz_limit = new Motion51Limits(plugin, this, x0=x,y, _("Horiz shake limit%"), + &plugin->config.horiz_limit, 0.f, 75.f, 72); + horiz_limit->create_objects(); + shake_fade = new Motion51Limits(plugin, this, x1,y, _("Shake fade%"), + &plugin->config.shake_fade, 0.f, 75.f, 72); + shake_fade->create_objects(); + y += horiz_limit->get_h() + 10; + vert_limit = new Motion51Limits(plugin, this, x0,y, _("Vert shake limit%"), + &plugin->config.vert_limit, 0.f, 75.f, 72); + vert_limit->create_objects(); + y += vert_limit->get_h() + 10; + twist_limit = new Motion51Limits(plugin, this, x0,y, _("Twist limit%"), + &plugin->config.twist_limit, 0.f, 75.f, 72); + twist_limit->create_objects(); + twist_fade = new Motion51Limits(plugin, this, x1,y, _("Twist fade%"), + &plugin->config.twist_fade, 0.f, 75.f, 72); + twist_fade->create_objects(); + y += twist_fade->get_h() + 20; + + add_subwindow(draw_vectors = new Motion51DrawVectors(plugin, this, x, y)); + add_subwindow(title = new BC_Title(x1, y, _("Tracking file:"))); + y += draw_vectors->get_h() + 5; + add_subwindow(enable_tracking = new Motion51EnableTracking(plugin, this, x, y)); + add_subwindow(tracking_file = new Motion51TrackingFile(plugin, + plugin->config.tracking_file, this, x1, y, get_w()-30-x1)); + y += enable_tracking->get_h() + 20; + + add_subwindow(reset_config = new Motion51ResetConfig(plugin, this, x0=x, y)); + add_subwindow(reset_tracking = new Motion51ResetTracking(plugin, this, x1, y)); + y += reset_config->get_h()+20; + + int pef = client->server->mwindow->edl->session->video_every_frame; + add_subwindow(pef_title = new BC_Title(x, y, + !pef ? _("For best results\n" + " Set: Play every frame\n" + " Preferences-> Playback-> Video Out") : + _("Currently using: Play every frame"), MEDIUMFONT, + !pef ? RED : GREEN)); + + show_window(1); +} + +void Motion51Window::update_gui() +{ + Motion51Config &config = plugin->config; + horiz_limit->update(config.horiz_limit); + vert_limit->update(config.vert_limit); + twist_limit->update(config.twist_limit); + shake_fade->update(config.shake_fade); + twist_fade->update(config.twist_fade); + + sample_r->update(config.sample_r); + char string[BCTEXTLEN]; + sprintf(string, "%d", config.sample_steps); + sample_steps->set_text(string); + + block_w->update(config.block_w); + block_h->update(config.block_h); + block_x->update(config.block_x); + block_y->update(config.block_y); + + draw_vectors->update(config.draw_vectors); + tracking_file->update(config.tracking_file); + enable_tracking->update(config.tracking); +} + +Motion51Limits::Motion51Limits(Motion51Main *plugin, Motion51Window *gui, int x, int y, + const char *ttext, float *value, float min, float max, int ttbox_w) + : BC_TumbleTextBox(gui, *value, min, max, x, y, ttbox_w) +{ + this->plugin = plugin; + this->gui = gui; + this->ttext = cstrdup(ttext); + this->value = value; + title = 0; +} + +Motion51Limits::~Motion51Limits() +{ + delete [] ttext; +} + +void Motion51Limits::create_objects() +{ + BC_TumbleTextBox::create_objects(); + int tx = BC_TumbleTextBox::get_x() + BC_TumbleTextBox::get_w() + 5; + int ty = BC_TumbleTextBox::get_y(); + gui->add_subwindow(title = new BC_Title(tx,ty,ttext)); +} + +int Motion51Limits::handle_event() +{ + *value = atof(get_text()); + plugin->send_configure_change(); + return 1; +} + +Motion51TrackingFile::Motion51TrackingFile(Motion51Main *plugin, + const char *filename, Motion51Window *gui, int x, int y, int w) + : BC_TextBox(x, y, w, 1, filename) +{ + this->plugin = plugin; + this->gui = gui; +}; + +int Motion51TrackingFile::handle_event() +{ + strcpy(plugin->config.tracking_file, get_text()); + plugin->send_configure_change(); + return 1; +} + + +Motion51SampleSteps::Motion51SampleSteps(Motion51Main *plugin, + int x, int y, int w) + : BC_PopupMenu(x, y, w, "", 1) +{ + this->plugin = plugin; +} + +void Motion51SampleSteps::create_objects() +{ + add_item(new BC_MenuItem("16")); + add_item(new BC_MenuItem("32")); + add_item(new BC_MenuItem("64")); + add_item(new BC_MenuItem("128")); + add_item(new BC_MenuItem("256")); + add_item(new BC_MenuItem("512")); + add_item(new BC_MenuItem("1024")); + add_item(new BC_MenuItem("2048")); + add_item(new BC_MenuItem("4096")); + add_item(new BC_MenuItem("8192")); + add_item(new BC_MenuItem("16384")); + add_item(new BC_MenuItem("32768")); + char string[BCTEXTLEN]; + sprintf(string, "%d", plugin->config.sample_steps); + set_text(string); +} + +int Motion51SampleSteps::handle_event() +{ + plugin->config.sample_steps = atoi(get_text()); + plugin->send_configure_change(); + return 1; +} + + + +Motion51DrawVectors::Motion51DrawVectors(Motion51Main *plugin, + Motion51Window *gui, int x, int y) + : BC_CheckBox(x, y, plugin->config.draw_vectors, _("Draw vectors")) +{ + this->gui = gui; + this->plugin = plugin; +} + +int Motion51DrawVectors::handle_event() +{ + plugin->config.draw_vectors = get_value(); + plugin->send_configure_change(); + return 1; +} + + +Motion51ResetConfig::Motion51ResetConfig(Motion51Main *plugin, Motion51Window *gui, int x, int y) + : BC_GenericButton(x, y, _("Reset defaults")) +{ + this->plugin = plugin; + this->gui = gui; +} + +int Motion51ResetConfig::handle_event() +{ + plugin->config.init(); + gui->update_gui(); + plugin->send_configure_change(); + return 1; +} + +Motion51ResetTracking::Motion51ResetTracking(Motion51Main *plugin, Motion51Window *gui, int x, int y) + : BC_GenericButton(x, y, _("Reset Tracking")) +{ + this->plugin = plugin; + this->gui = gui; +} + +int Motion51ResetTracking::handle_event() +{ + plugin->set_tracking_path(); + gui->tracking_file->update(plugin->config.tracking_file); + plugin->config.tracking = 0; + plugin->send_configure_change(); + gui->enable_tracking->update(0); + ::remove(plugin->config.tracking_file); + return 1; +} + +Motion51EnableTracking::Motion51EnableTracking(Motion51Main *plugin, Motion51Window *gui, int x, int y) + : BC_CheckBox(x, y, plugin->config.tracking,_("Enable Tracking")) +{ + this->plugin = plugin; + this->gui = gui; +} + +int Motion51EnableTracking::handle_event() +{ + plugin->config.tracking = get_value(); + plugin->send_configure_change(); + return 1; +} + diff --git a/cinelerra-5.1/plugins/motion51/motionwindow51.h b/cinelerra-5.1/plugins/motion51/motionwindow51.h new file mode 100644 index 00000000..faf94053 --- /dev/null +++ b/cinelerra-5.1/plugins/motion51/motionwindow51.h @@ -0,0 +1,125 @@ + +/* + * CINELERRA + * Copyright (C) 2008 Adam Williams + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "guicast.h" +#include "motion51.inc" + + +class Motion51SampleSteps : public BC_PopupMenu +{ +public: + Motion51SampleSteps(Motion51Main *plugin, int x, int y, int w); + void create_objects(); + int handle_event(); + Motion51Main *plugin; +}; + + +class Motion51DrawVectors : public BC_CheckBox +{ +public: + Motion51DrawVectors(Motion51Main *plugin, Motion51Window *gui, int x, int y); + int handle_event(); + Motion51Main *plugin; + Motion51Window *gui; +}; + +class Motion51TrackingFile : public BC_TextBox +{ +public: + Motion51TrackingFile(Motion51Main *plugin, const char *filename, + Motion51Window *gui, int x, int y, int w); + int handle_event(); + Motion51Main *plugin; + Motion51Window *gui; +}; + +class Motion51Limits : public BC_TumbleTextBox { +public: + Motion51Limits(Motion51Main *plugin, Motion51Window *gui, int x, int y, + const char *ttext, float *value, float min, float max, int ttbox_w); + ~Motion51Limits(); + + void create_objects(); + int handle_event(); + + Motion51Main *plugin; + Motion51Window *gui; + float *value; + const char *ttext; + BC_Title *title; + + int get_w() { return BC_TumbleTextBox::get_w()+5+title->get_w(); } + int get_h() { return BC_TumbleTextBox::get_h(); } +}; + +class Motion51ResetConfig : public BC_GenericButton +{ +public: + Motion51ResetConfig(Motion51Main *plugin, Motion51Window *gui, int x, int y); + int handle_event(); + Motion51Main *plugin; + Motion51Window *gui; +}; + +class Motion51ResetTracking : public BC_GenericButton +{ +public: + Motion51ResetTracking(Motion51Main *plugin, Motion51Window *gui, int x, int y); + int handle_event(); + Motion51Main *plugin; + Motion51Window *gui; +}; + +class Motion51EnableTracking : public BC_CheckBox +{ +public: + Motion51EnableTracking(Motion51Main *plugin, Motion51Window *gui, int x, int y); + int handle_event(); + Motion51Main *plugin; + Motion51Window *gui; +}; + +class Motion51Window : public PluginClientWindow +{ +public: + Motion51Window(Motion51Main *plugin); + ~Motion51Window(); + + void create_objects(); + void update_gui(); + + Motion51Main *plugin; + Motion51Limits *horiz_limit, *vert_limit, *twist_limit; + Motion51Limits *shake_fade, *twist_fade; + Motion51Limits *sample_r; + Motion51SampleSteps *sample_steps; + Motion51Limits *block_x, *block_y, *block_w, *block_h; + Motion51DrawVectors *draw_vectors; + Motion51TrackingFile *tracking_file; + Motion51ResetConfig *reset_config; + Motion51ResetTracking *reset_tracking; + Motion51EnableTracking *enable_tracking; + + BC_Title *pef_title; +}; + + diff --git a/cinelerra-5.1/plugins/motion51/picon.png b/cinelerra-5.1/plugins/motion51/picon.png new file mode 100644 index 0000000000000000000000000000000000000000..128c5d580b36594ed39c111d365f4f95265a2f34 GIT binary patch literal 5999 zcmV-#7m( zK~!kowR&lkUDb8wx6ir5o5z}^DwWVY*&rm0gqVyN4TNCqG&Xi@27^29h12dhUd#4c zl^8o@brN^SX$&4>aAFbiTL{JuH=moc(jhP-1Y?D-qZ_~>5FbDQpcSqvfEs{l0CfOOW_EuaFvbu8 z;6;Ew1XxdDNK1gp!H{?fu#;d{fM^}SzrivVfDC{lfKC7%;W-!nJr!PK#z4t%i4dF$ zR|`M`Kn1{L2KT@3+@hoDY|YdGeP9ufUJ z0}x|KFu(#sd={%jE>i^19fDX4;2Z#b&|C(Ph9J9r40bbsq>d43?`AV|?g-6{DrvV~ zv`pKP)rFU+)bZC0ZKdFdcPqC(5BZfxIjMUTxmh26Ge9oy0;OgwSxf>L1#kqwn*ff5 zKxgJ!DvAl51xy6Q41>K80_qO^yaPZU%o#|LfPOtCa)z`K$ZWC!x7aY-TOLZL&%fFa zhR0=dYDAWnU8cR!NitOrQe(dKxr=DQF@^L1V~dm0e^7Q6k6I>`?C>}9;qPWc53$3f zaytDp^E+aS&f;bfm_+Q0dqZ~{1TX+#J~S60C=IneuYI2=VF>q?Es3Dwiqxf7dH%L1 zZTZ$6WL!C=N0*u0frbD~l2c7WA`7uk0_z$CmDbQ3G)s?xVL@WaY!+17hZIg2JEuYm z?FFzewA3j84iTw$Sm+FHW`;454%2ZrfG)J_eQc*YNr*C_(m`9NC;{6gP4TW@925Q{~rJg)O=_Lci$q|9h zw+Mb0h}%#$gceC*Hesl=CSqbi zob~IJvF3xm@xdFtx{=bxx%~lGTkLQ7lA)Y7{6`)%@aF$ECd58t^hMbCUOU;W)Kg5z zgbAn{z&rpwVO(bbm|=~4+d?rqV}+K9if4z@;;!jQn%}$lJc&diX9hdVUc*nN76xQ~ z>BlJ}$neRpeW#VKosgx@{i6NQH;iKH!(6-j29>I&s4!3fp0K3KexJ;25_s8QamyN& z`sr5bZdjvbO0s?F`#pXtFTGNM;iX{z!%sx=!fhb-TX!&?j7^`emedN1V z@|qRCEUag})fMXH?-zci4EFXHG)cRHD{bU<*LVo?QDY>D8bpp8uKsqGs8DhuyBH>zxS|dFH7mf`8Vs~ zUu~wY6Q`h8Oz8y53S_F#8WGEIVx-ms5Qtc|qZp}7MoEDcXEAfM6GulnQC)3FhZ5e= z-Rb1{3n;nzCgoX|fcH;G=kQ*0deuT2vwvwgfAy@+@>oW=Oz8_}Xk*si*cM<>DT`BTB zN)Fzo3)LF+K7X82>o%+QM{d=Dxu4h@!&qgBp8EKDrVT4t*O;3J#tD@+P3=1=j@4#_ z_4_Nd3bF)~2``Qct8aist_!)ZJ|?Y;_HmF;qwSrN9u8ZRlYw1%9%Zrn$UG=jfYSLG zk4#Xk?nJRuit)7p1bP65=+~(9se`Zdn52?H#|2_)WZ3FxN{E@CiIbOI>!)7bX9Pd~ zr6}Bcbs!G}qCPw=JPcFrLu<7$W5U~Zf>hO|^tO?yXIu5T3UqFOVbZ48*3_7c-!m@;Az zBk{CNF-B~iPTht`;mdC<=GXU>YTx=&NA0HHD`C$m!(oHO?#GOS@4YK1{{1qeuppy4 zu3Q&PWH)HH^(Jx|Tk{8Q41(#B-v61cGSStiQ`_eiYh6Pim z!V;4Lkd$GD6ES9x4C_xBKo^wmX3zsj1~4;!gV#Ip0#&)mG?EI8qE!V^ zBkZZ_0Gi?q%tyWhoD_CT9nCeL`(+9TM~v|=eo}FLoA8HSQulr~NVO%7{@u^z!jIkO zCY8_AKhFvKlh^ucUWJmc9+vf$2}~}@V1_lFQUH4a>wExnq|MD*r<%E9xj*5%EV|dJ z$t{mq8M~Y3uUX96NrF{|8hcJq_ohqr{=F}YLUSLTpiBK?B^R`hOeT3Y?xU99jwc0# zja`%+%vhUkvXe22+sAFu(T)CpdtJzk4LiF)^I$>Si7J=@!U zspJLhT+|O8WJC$8&~|spE(RB^c{(Z1^+o0nJ-PC=}`I-TMgEezzu0W-ODyGO)xyWw{Linv`oeVYJ`#iLIrD>wgjuG#s08vKzX zoQ6lozxSwF*z1yM3m6|M_=zc-lYs%POu-^S>eS1!iD3!NBzEP*^w*xSblnwzoc)c5 zbpqIDLJFZ9jwE|Su;f3j@b&-MYoyLu!x;3j>PLSZbg#>D`BN7MhimIaecuIvO&OKB zw4?4Ww>1-@<}-3TY0R+@V!&omYfY%G#iGrocWVAh8oeaP_Mv{+x%MVsHS>Dp&!0Ev ze|U8;Ph64^PV}a?q&*mVjoY zRX1yY7}Sx3_fNZNQA{Zcpr28aWCiTLvYw~{>7@%>O8{eZc zYZhrvA6d4OVS66V$+4lN^2zZZga*K3&h?tz5yfN4&h zvp}Ex$_-wA*>&2g4v0_O@vX$X(Qzs+FL}yHYPjYs?-dG2R%m$ZUaB5z5w7ghv}qm_ zR|%Stxso)n8iH+ugpk30ZQ_=t%72v1wv$C_;Z1t-+d_WkrGF5Axcu6n^O+@571nxA zXOii?R()Wjm=S_#j(73dETm5BXPSe<8?SBZA73!VpMJsX%NjxAk6*IB{=_}fzLNci z4{WLNVQoO|HwBSqum-@Ih&_;&Dg~!)qwz4r__ImTl4)oH2@8U?1hj^lazy%G>3WZo znYb<+6gq1B;6s0Eb|sJK|Fz(Lb^Dj^w-2^z+Q?^u`6#Q&WubZ7$^aS_#A0>s?r2m7A^!BYla%WkK3n#SDt_J#)WLro& z4~@X*Od?MwR=%NET<|W+fw)DLzmH8 zuf9b`_U)Gc{nLNhDtw@`&c0$(u_`4DH)fe2+$#f6;*2WmEb{2$Kwtkxvz43BQ_i4o zupiW~8gRe6^?50e_Upt4m%7t!kIW0En^494WE-OfjTms9k8Nq#WbL*A2|1j+eCIpWjM9cM7-t+1(9)Yo{205T>6y zqyQ*UobMZnO>L6W7vw7hhq$mXuXpX*0k=9$+LG*kwL=EKTFlAh#-RBv*JwUBE%c0r z>k3YHE!D6S`ZGWNa|5!V?tbBKjgD{5uPpkLOe0gaU9H(ALYv?2f!uIKjh-jw_@IuV z4MDM!H#4pKpMD%){_G3FvITe)UCfV%+_dwq$DV|*e;aOnyk^jE&I(o^_{%t z^P4l(kPGFi`2d!LvQ_seKh>Yw zad9^PRJDXZ9Su$xhS@*1THr^A)B5sm7t1_f(FB`fsWXSQ+W;($K#1=f*2|CWF%=ja zm#tAct9f#!0j@H*=<$F4!Dp;{AGo6~zG5b~kGv*6`}7};>2B2)Qo6C0y9uQoCaRI3 zMyR4r#MRYVO?y@XiY()Ws|~<`X2;WQol?X4pI>}6^@acTOlrQLbD$>uhmO5O;}5$@ zmT#D*SgL@E0NU|D^#HiqdjY{EN4+*Xx?Xemub=-JOn zg|dDB1OJ%)+#}akPW|gjtMG@qmY65}cksjg+f!Jv!G{I~bjSpckiiM%{l(gZyqF!D z`4wn$T|2@Ll19G^xu_1H#er&*zzl?}h8wPhns#(<#D--J04;HbbnV6(rYa0kQzLL| zKcHOZqpwd`J#O=^gF}|I4wAL9yQ#`D_}twW)lZhn-1T4mrk!O8KzMaX1q4vq#flA6 z0Gh1^>XVbAVTj8{@%O8I^9%AwaOo?x+6~Usq>ZVhdC7?99d5Mol=tRduR(?!#!gwq zPtgW&JQVDY0yq&0_@kkP8exb8G)Fl#Lsu)tbcJpJ9SYShjRSqgfozhNuQIAX+_iA@ zxX3U4^t&4T^Oz>Nfd;{g?=#K?=XOz znJzU`yLEBmb%+G7Ll4Jg<7yNjJZ9kws6L3AvbbAK(e1s8MW(-`$3w*_aU2ddEhEP*E(*5#=M1 z-kUQ;=ea|#cK{+H9nLYM33V+tnh!4_gCvMFiXFFN_~q@d7mL*{(LCIttaJNGjIhM@ zuB{ zWZA-|0h?p%b#=3*l0N10?__V7CFKAg~WU}Va~NP&X-W5|e`o0A68 zt#M2Q{m8ahC)y-VCxdD@-5v%o63$O+0GdP;{D)jF=6cM`e$1M4A8|2aog)MAN2+UR z@S=nMp%(1k1CU}ZITLM|*3FFuqoUe-PTH9xlWE*g5hSi}#lFXk3x=_0gak9wZ<`d1 z-{P9chug6^lEgdZBaVt)w?#g!UsmmRx!CHVHURESA^h zjgEE8dvVdvUD6%s>WP!rs3t}H z(TM?7|IY!^hF|GH(ul z^Um}r*>43Xd^!*md*