/* * CINELERRA * Copyright (C) 1997-2015 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 #include #include #include #include #include "arraylist.h" #include "bccmodels.h" #include "bccolors.h" #include "clip.h" #include "edl.h" #include "edlsession.h" #include "filexml.h" #include "keyframe.h" #include "keyframes.h" #include "tracer.h" #include "transportque.inc" #include "tracerwindow.h" #include "language.h" #include "vframe.h" REGISTER_PLUGIN(Tracer) void tracer_pgm(const char *fn,VFrame *vfrm) { FILE *fp = fopen(fn,"w"); int w = vfrm->get_w(), h = vfrm->get_h(); fprintf(fp,"P5\n%d %d\n255\n",w,h); fwrite(vfrm->get_data(),w,h,fp); fclose(fp); } TracerPoint::TracerPoint(float x, float y) { this->x = x; this->y = y; } TracerPoint::~TracerPoint() { } int Tracer::new_point() { EDLSession *session = get_edl()->session; float x = !session ? 0.f : session->output_w / 2.f; float y = !session ? 0.f : session->output_h / 2.f; return config.add_point(x, y); } TracerConfig::TracerConfig() { draw = 1; fill = 0; feather = 0; radius = 1; invert = 0; } TracerConfig::~TracerConfig() { } int TracerConfig::equivalent(TracerConfig &that) { if( this->draw != that.draw ) return 0; if( this->fill != that.fill ) return 0; if( this->feather != that.feather ) return 0; if( this->invert != that.invert ) return 0; if( this->radius != that.radius ) return 0; if( this->points.size() != that.points.size() ) return 0; for( int i=0, n=points.size(); ipoints[i], *bp = that.points[i]; if( !EQUIV(ap->x, bp->x) ) return 0; if( !EQUIV(ap->y, bp->y) ) return 0; } return 1; } void TracerConfig::copy_from(TracerConfig &that) { this->draw = that.draw; this->fill = that.fill; this->feather = that.feather; this->invert = that.invert; this->radius = that.radius; points.remove_all_objects(); for( int i=0,n=that.points.size(); ix, pt->y); } } void TracerConfig::interpolate(TracerConfig &prev, TracerConfig &next, long prev_frame, long next_frame, long current_frame) { copy_from(prev); } void TracerConfig::limits() { } int TracerConfig::add_point(float x, float y) { int i = points.size(); points.append(new TracerPoint(x, y)); return i; } void TracerConfig::del_point(int i) { points.remove_object_number(i); } Tracer::Tracer(PluginServer *server) : PluginVClient(server) { frm = 0; frm_rows = 0; msk = 0; msk_rows = 0; edg = 0; edg_rows = 0; w = 0; w1 = w-1; h = 0; h1 = h-1; color_model = bpp = 0; is_float = is_yuv = 0; comps = comp = 0; ax = 0; ay = 0; bx = 0; by = 0; cx = 0; cy = 0; ex = 0; ey = 0; drag = 0; selected = 0; } Tracer::~Tracer() { delete edg; delete msk; } const char* Tracer::plugin_title() { return N_("Tracer"); } int Tracer::is_realtime() { return 1; } NEW_WINDOW_MACRO(Tracer, TracerWindow); int Tracer::load_configuration1() { KeyFrame *prev_keyframe = get_prev_keyframe(get_source_position()); if( prev_keyframe->position == get_source_position() ) { read_data(prev_keyframe); return 1; } return load_configuration(); } LOAD_CONFIGURATION_MACRO(Tracer, TracerConfig); void Tracer::render_gui(void *data) { Tracer *tracer = (Tracer *)data; tracer->drag = drag; } int Tracer::is_dragging() { drag = 0; send_render_gui(this); return drag; } void TracerConfig::save_data(KeyFrame *keyframe) { FileXML output; // cause data to be stored directly in text output.set_shared_output(keyframe->xbuf); output.tag.set_title("TRACER"); output.tag.set_property("DRAW", draw); output.tag.set_property("FILL", fill); output.tag.set_property("FEATHER", feather); output.tag.set_property("RADIUS", radius); output.tag.set_property("INVERT", invert); output.append_tag(); output.append_newline(); output.tag.set_title("/TRACER"); output.append_tag(); output.append_newline(); for( int i=0, n=points.size(); ix); output.tag.set_property("Y", pt->y); output.append_tag(); output.tag.set_title(point+0); output.append_tag(); output.append_newline(); } output.terminate_string(); } void Tracer::save_data(KeyFrame *keyframe) { config.save_data(keyframe); } void TracerConfig::read_data(KeyFrame *keyframe) { FileXML input; input.set_shared_input(keyframe->xbuf); points.remove_all_objects(); int result = 0; while( !(result=input.read_tag()) ) { if( input.tag.title_is("TRACER") ) { draw = input.tag.get_property("DRAW", draw); fill = input.tag.get_property("FILL", fill); feather = input.tag.get_property("FEATHER", feather); radius = input.tag.get_property("RADIUS", radius); invert = input.tag.get_property("INVERT", invert); limits(); } else if( !strncmp(input.tag.get_title(),"POINT_",6) ) { float x = input.tag.get_property("X", 0.f); float y = input.tag.get_property("Y", 0.f); add_point(x, y); } } } void Tracer::read_data(KeyFrame *keyframe) { config.read_data(keyframe); } void Tracer::span_keyframes(KeyFrame *src, int64_t start, int64_t end) { TracerConfig src_config; src_config.read_data(src); KeyFrames *keyframes = (KeyFrames *)src->autos; KeyFrame *prev = keyframes->get_prev_keyframe(start, PLAY_FORWARD); TracerConfig prev_config; prev_config.read_data(prev); // Always update the first one update_parameter(prev_config, src_config, prev); KeyFrame *curr = (KeyFrame*)prev->next; while( curr && curr->position < end ) { update_parameter(prev_config, src_config, curr); curr = (KeyFrame*)curr->next; } } void TracerPoint::update_parameter(TracerPoint *prev, TracerPoint *src) { if( prev->x != src->x ) x = src->x; if( prev->y != src->y ) y = src->y; } void Tracer::update_parameter(TracerConfig &prev_config, TracerConfig &src_config, KeyFrame *keyframe) { TracerConfig dst_config; dst_config.read_data(keyframe); if( prev_config.draw != src_config.draw ) dst_config.draw = src_config.draw; if( prev_config.fill != src_config.fill ) dst_config.fill = src_config.fill; if( prev_config.feather != src_config.feather ) dst_config.feather = src_config.feather; if( prev_config.invert != src_config.invert ) dst_config.invert = src_config.invert; if( prev_config.radius != src_config.radius ) dst_config.radius = src_config.radius; int src_points = src_config.points.size(); int dst_points = dst_config.points.size(); int prev_points = prev_config.points.size(); int npoints = bmin(prev_points, bmin(src_points, dst_points)); for( int i=0; iupdate_parameter(prev_point, src_point); } dst_config.save_data(keyframe); } void Tracer::update_gui() { if( !thread ) return; thread->window->lock_window("Tracer::update_gui"); TracerWindow *window = (TracerWindow*)thread->window; if( load_configuration1() ) { window->update_gui(); window->flush(); } thread->window->unlock_window(); } void Tracer::draw_point(TracerPoint *pt) { int d = bmax(w,h) / 200 + 2; int r = d/2+1, x = pt->x, y = pt->y; frm->draw_smooth(x-r,y+0, x-r, y-r, x+0,y-r); frm->draw_smooth(x+0,y-r, x+r, y-r, x+r,y+0); frm->draw_smooth(x+r,y+0, x+r, y+r, x+0,y+r); frm->draw_smooth(x+0,y+r, x-r, y+r, x-r,y+0); } void Tracer::draw_points() { for( int i=0, n=config.points.size(); iset_pixel_color(selected == i ? GREEN : WHITE); draw_point(pt); } } void Tracer::draw_edge() { float scale = 1 / 255.0f; int color_model = frm->get_color_model(); int bpp = BC_CModels::calculate_pixelsize(color_model); switch( color_model ) { case BC_RGB_FLOAT: for( int y=0; y 0 ) PIX_GRADIENT(type,-1, iy); \ if( iy != 0) PIX_GRADIENT(type, 0, iy); \ if( vx < w1) PIX_GRADIENT(type, 1, iy); \ } while(0) #define MAX_GRADIENT(type) do { \ type *xp = (type *)(frm_rows[vy] + vx*bpp); \ if( vy > 0 ) ROW_GRADIENT(type,-1); \ ROW_GRADIENT(type, 0); \ if( vy < h1 ) ROW_GRADIENT(type, 1); \ } while(0) #define MAX_PIXEL(type, ix, iy) do { \ int vx = cx + ix, vy = cy + iy; \ if( edg_rows[vy][vx] ) break; \ float vv = FLT_MAX; \ int dx = ex-vx, dy = ey-vy; \ int rr = dx*dx + dy*dy; \ if( rr > dd ) break; \ if( rr > 0 ) { \ float r = (float)(ix*dx + iy*dy) / rr; \ float vmax = 0; \ MAX_GRADIENT(type); \ vv = r + vmax; \ } \ if( maxv < vv ) { \ maxv = vv; \ nx = vx; ny = vy; \ } \ } while(0) #define ROW_MAX(type, iy) do { \ if( cx > 0 ) MAX_PIXEL(type,-1, iy); \ if( iy != 0 ) MAX_PIXEL(type, 0, iy); \ if( cx < w1 ) MAX_PIXEL(type, 1, iy); \ } while(0) int Tracer::step() { int ret = 0; if( !edg_rows[cy][cx] ) { points.add(cx,cy); edg_rows[cy][cx] = 0xff; } int dx = ex-cx, dy = ey-cy; int dd = dx*dx + dy*dy; if( !dd ) return ret; int nx = cx, ny = cy; double maxv = -FLT_MAX; if( cy > 0 ) ROW_MAX(uint8_t,-1); ROW_MAX(uint8_t, 0); if( cy < h1 ) ROW_MAX(uint8_t, 1); cx = nx; cy = ny; return maxv > 0 ? 1 : 0; } void Tracer::trace(int i0, int i1) { TracerPoint *pt0 = config.points[i0]; TracerPoint *pt1 = config.points[i1]; cx = pt0->x; bclamp(cx, 0, w1); cy = pt0->y; bclamp(cy, 0, h1); ex = pt1->x; bclamp(ex, 0, w1); ey = pt1->y; bclamp(ey, 0, h1); while( step() ); } int Tracer::smooth() { int &n = points.total, m = 0; if( n < 3 ) return m; TracePoint *ap; TracePoint *bp = &points[0]; TracePoint *cp = &points[1]; for( ; n>=3; --n ) { ap = &points[n-1]; if( abs(ap->x-cp->x)<2 && abs(ap->y-cp->y)<2 ) { edg_rows[bp->y][bp->x] = 0; ++m; } else break; } if( n < 3 ) return m; ap = &points[0]; bp = &points[1]; for( int i=2; ix-cp->x)<2 && abs(ap->y-cp->y)<2 && ( (bp->x==ap->x || bp->x==cp->x) && (bp->y==ap->y || bp->y==cp->y) ) ) { edg_rows[bp->y][bp->x] = 0; ++m; } else { ++ap; ++bp; } bp->x = cp->x; bp->y = cp->y; } n -= m; return m; } #if 0 int winding2(int x, int y, TracePoints &pts, int n) { int w = 0; int x0 = pts[0].x-x, y0 = pts[0].y-y; for( int x1,y1,i=1; i 0 ) w += y0<0 ? 2 : -2; // crosses x on plus side } else if( !y0 && x0 > 0 ) w += y1>0 ? 1 : -1; else if( !y1 && x1 > 0 ) w += y0>0 ? 1 : -1; } return w; } #endif class FillRegion { class segment { public: int y, lt, rt; }; ArrayList stack; void push(int y, int lt, int rt) { segment &seg = stack.append(); seg.y = y; seg.lt = lt; seg.rt = rt; } void pop(int &y, int <, int &rt) { segment &seg = stack.last(); y = seg.y; lt = seg.lt; rt = seg.rt; stack.remove(); } int w, h; uint8_t *edg; uint8_t *msk; bool edge_pixel(int i) { return edg[i] > 0; } public: void fill(int x, int y); void run(); FillRegion(VFrame *edg, VFrame *msk); }; FillRegion::FillRegion(VFrame *edg, VFrame *msk) { this->w = msk->get_w(); this->h = msk->get_h(); this->msk = (uint8_t*) msk->get_data(); this->edg = (uint8_t*) edg->get_data(); } void FillRegion::fill(int x, int y) { push(y, x, x); } void FillRegion::run() { while( stack.size() > 0 ) { int y, ilt, irt; pop(y, ilt, irt); int ofs = y*w + ilt; for( int x=ilt; x<=irt; ++x,++ofs ) { if( msk[ofs] ) continue; msk[ofs] = 0xff; if( edge_pixel(ofs) ) continue; int lt = x, rt = x; int lofs = ofs; for( int i=lt; --i>=0; lt=i ) { if( msk[--lofs] ) break; msk[lofs] = 0xff; if( edge_pixel(lofs) ) break; } int rofs = ofs; for( int i=rt; ++i< w; rt=i ) { if( msk[++rofs] ) break; msk[rofs] = 0xff; if( edge_pixel(rofs) ) break; } if( y+1 < h ) push(y+1, lt, rt); if( y-1 >= 0 ) push(y-1, lt, rt); } } } void Tracer::feather(int r, double s) { if( !r ) return; int dir = r < 0 ? (r=-r, -1) : 1; int rr = r * r; int psf[rr]; // pt spot fn float p = powf(10.f, s/2); for( int i=0; i 255 ) vv = 255; psf[i] = vv; } for( int i=0,n=points.size(); ix-r, xn=pt->x+r; bclamp(xs, 0, w); bclamp(xn, 0, w); int ys = pt->y-r, yn=pt->y+r; bclamp(ys, 0, h); bclamp(yn, 0, h); for( int y=ys ; yx, dy = y-pt->y; int dd = dx*dx + dy*dy; if( dd >= rr ) continue; int v = psf[dd], px = msk_rows[y][x]; if( dir < 0 ? pxv ) msk_rows[y][x] = v; } } } } void Tracer::draw_mask() { switch( color_model ) { case BC_RGB888: for( int y=0; yget_rows(); w = frm->get_w(); w1 = w-1; h = frm->get_h(); h1 = h-1; color_model = frm->get_color_model(); bpp = BC_CModels::calculate_pixelsize(color_model); is_float = BC_CModels::is_float(color_model); is_yuv = BC_CModels::is_yuv(color_model); has_alpha = BC_CModels::has_alpha(color_model); comps = BC_CModels::components(color_model); comp = bmin(comps, 3); read_frame(frm, 0, start_position, frame_rate, 0); if( !edg ) redraw = 1; VFrame::get_temp(edg, w, h, BC_GREY8); if( redraw ) { edg->black_frame(); edg_rows = edg->get_rows(); int n = config.points.size()-1; points.clear(); for( int i=0; i 0 ) trace(n,0); while( smooth() > 0 ); if( config.fill && points.size() > 2 ) { int l = points.size(), l2 = l/2; TracePoint *pt0 = &points[0], *pt1 = &points[l2]; int cx = (pt0->x+pt1->x)/2, cy = (pt0->y+pt1->y)/2; VFrame::get_temp(msk, w, h, BC_GREY8); msk->black_frame(); msk_rows = msk->get_rows(); FillRegion fill_region(edg, msk); fill_region.fill(cx, cy); fill_region.run(); feather(config.feather, config.radius); } } if( config.fill && msk ) draw_mask(); if( config.draw && edg ) draw_edge(); if( is_dragging() ) draw_points(); return 0; }