1 /* === S Y N F I G ========================================================= */
3 ** \brief Preview implementation file
8 ** Copyright (c) 2002-2005 Robert B. Quattlebaum Jr., Adrian Bentley
9 ** Copyright (c) 2007 Chris Moore
11 ** This package is free software; you can redistribute it and/or
12 ** modify it under the terms of the GNU General Public License as
13 ** published by the Free Software Foundation; either version 2 of
14 ** the License, or (at your option) any later version.
16 ** This package is distributed in the hope that it will be useful,
17 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
18 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19 ** General Public License for more details.
22 /* ========================================================================= */
24 /* === H E A D E R S ======================================================= */
35 #include "audiocontainer.h"
36 #include <gtkmm/stock.h>
37 #include <gtkmm/separator.h>
39 #include <synfig/target_scanline.h>
40 #include <synfig/surface.h>
43 #include "asyncrenderer.h"
49 /* === U S I N G =========================================================== */
53 using namespace synfig;
54 using namespace studio;
56 /* === M A C R O S ========================================================= */
58 /* === G L O B A L S ======================================================= */
60 /* === P R O C E D U R E S ================================================= */
62 /* === M E T H O D S ======================================================= */
64 /* === E N T R Y P O I N T ================================================= */
66 class studio::Preview::Preview_Target : public Target_Scanline
70 sigc::signal<void, const Preview_Target *> signal_frame_done_;
85 nframes = curframe = 0;
88 const RendDesc &get_rend_desc() const { return desc; }
90 virtual bool set_rend_desc(RendDesc *r)
92 if(Target_Scanline::set_rend_desc(r))
94 /*synfig::warning("Succeeded in setting the desc to new one: %d x %d, %.2f fps [%.2f,%.2f]",
95 desc.get_w(),desc.get_h(),desc.get_frame_rate(),
96 (float)desc.get_time_start(),(float)desc.get_time_end());*/
98 surface.set_wh(desc.get_w(),desc.get_h());
101 nframes = (int)floor((desc.get_time_end() - desc.get_time_start())*desc.get_frame_rate());
103 tbegin = desc.get_time_start();
104 tend = tbegin + nframes/desc.get_frame_rate();
111 virtual bool start_frame(ProgressCallback */*cb*/=NULL)
116 virtual void end_frame()
118 //ok... notify our subscribers...
119 signal_frame_done_(this);
121 //synfig::warning("Finished the frame stuff, and changed time to %.3f",t);
124 virtual Color * start_scanline(int scanline)
126 return surface[scanline];
129 virtual bool end_scanline() {return true;}
131 sigc::signal<void, const Preview_Target *> &signal_frame_done() {return signal_frame_done_;}
133 const Surface &get_surface() const {return surface;}
135 float get_time() const
137 double time = ((nframes-curframe)/(double)nframes)*tbegin
138 + ((curframe)/(double)nframes)*tend;
143 studio::Preview::Preview(const studio::CanvasView::LooseHandle &h, float zoom, float f)
144 :canvasview(h),zoom(zoom),fps(f)
150 void studio::Preview::set_canvasview(const studio::CanvasView::LooseHandle &h)
156 //perhaps reset override values...
157 const RendDesc &r = canvasview->get_canvas()->rend_desc();
158 if(r.get_frame_rate())
160 float rate = 1/r.get_frame_rate();
161 overbegin = false; begintime = r.get_time_start() + r.get_frame_start()*rate;
162 overend = false; endtime = r.get_time_start() + r.get_frame_end()*rate;
167 studio::Preview::~Preview()
169 signal_destroyed_(this); //tell anything that attached to us, we're dying
172 void studio::Preview::render()
176 //render using the preview target
177 etl::handle<Preview_Target> target = new Preview_Target;
179 //connect our information to his...
180 //synfig::warning("Connecting to the end frame function...");
181 target->signal_frame_done().connect(sigc::mem_fun(*this,&Preview::frame_finish));
184 //synfig::warning("Setting Canvas");
185 target->set_canvas(get_canvas());
186 target->set_quality(quality);
189 RendDesc desc = get_canvas()->rend_desc();
191 //set the global fps of the preview
192 set_global_fps(desc.get_frame_rate());
196 int neww = (int)floor(desc.get_w()*zoom+0.5),
197 newh = (int)floor(desc.get_h()*zoom+0.5);
200 /*synfig::warning("Setting the render description: %d x %d, %f fps, [%f,%f]",
201 neww,newh,newfps, overbegin?begintime:(float)desc.get_time_start(),
202 overend?endtime:(float)desc.get_time_end());*/
206 desc.set_frame_rate(newfps);
210 desc.set_time_start(std::max(begintime,(float)desc.get_time_start()));
211 //synfig::warning("Set start time to %.2f...",(float)desc.get_time_start());
215 desc.set_time_end(std::min(endtime,(float)desc.get_time_end()));
216 //synfig::warning("Set end time to %.2f...",(float)desc.get_time_end());
219 //setting the description
221 //HACK - add on one extra frame because the renderer can't render the last frame
222 desc.set_time_end(desc.get_time_end() + 1.000001/fps);
224 target->set_rend_desc(&desc);
226 //... first we must clear our current selves of space
229 //now tell it to go... with inherited prog. reporting...
230 //synfig::info("Rendering Asynchronously...");
231 if(renderer) renderer->stop();
232 renderer = new AsyncRenderer(target);
237 static void free_guint8(const guint8 *mem)
242 void studio::Preview::frame_finish(const Preview_Target *targ)
244 //copy image with time to next frame (can just push back)
246 float time = targ->get_time();
247 const Surface &surf = targ->get_surface();
248 const RendDesc& r = targ->get_rend_desc();
250 //synfig::warning("Finished a frame at %f s",time);
253 PixelFormat pf(PF_RGB);
254 const int total_bytes(r.get_w()*r.get_h()*synfig::channels(pf));
256 //synfig::warning("Creating a buffer");
257 unsigned char *buffer((unsigned char*)malloc(total_bytes));
262 //convert all the pixels to the pixbuf... buffer... thing...
263 //synfig::warning("Converting...");
264 convert_color_format(buffer, surf[0], surf.get_w()*surf.get_h(), pf, App::gamma);
268 //uses and manages the memory for the buffer...
269 //synfig::warning("Create a pixmap...");
271 Gdk::Pixbuf::create_from_data(
272 buffer, // pointer to the data
273 Gdk::COLORSPACE_RGB, // the colorspace
274 ((pf&PF_A)==PF_A), // has alpha?
275 8, // bits per sample
276 surf.get_w(), // width
277 surf.get_h(), // height
278 surf.get_w()*synfig::channels(pf), // stride (pitch)
279 sigc::ptr_fun(free_guint8)
282 //add the flipbook element to the list (assume time is correct)
283 //synfig::info("Prev: Adding %f s to the list", time);
284 frames.push_back(fe);
289 #define IMAGIFY_BUTTON(button,stockid,tooltip) \
290 icon=manage(new Gtk::Image(Gtk::StockID(stockid),Gtk::ICON_SIZE_BUTTON)); \
291 button->add(*icon); \
292 tooltips.set_tip(*button,tooltip); \
293 icon->set_padding(0,0);\
296 Widget_Preview::Widget_Preview()
297 :Gtk::Table(5,5,false),
298 adj_time_scrub(0,0,1000,1,10,0),
299 scr_time_scrub(adj_time_scrub),
300 b_loop(/*_("Loop")*/),
307 //connect to expose events
308 //signal_expose_event().connect(sigc::mem_fun(*this, &studio::Widget_Preview::redraw));
310 //manage all the change in values etc...
311 adj_time_scrub.signal_value_changed().connect(sigc::mem_fun(*this,&Widget_Preview::slider_move));
312 scr_time_scrub.signal_event().connect(sigc::mem_fun(*this,&Widget_Preview::scroll_move_event));
313 draw_area.signal_expose_event().connect(sigc::mem_fun(*this,&Widget_Preview::redraw));
315 disp_sound.set_time_adjustment(&adj_sound);
318 //Set up signals to modify time value as it should be...
319 disp_sound.signal_start_scrubbing().connect(sigc::mem_fun(*this,&Widget_Preview::scrub_updated));
320 disp_sound.signal_scrub().connect(sigc::mem_fun(*this,&Widget_Preview::scrub_updated));
323 ---------------------------------
331 ---------------------------------
332 |loop|play|stop | hbox
333 |lastl|lastt|rerender|haltrend | hbox
339 Gtk::Button *button = 0;
340 Gtk::Image *icon = 0;
342 //should set up the dialog using attach etc.
343 attach(draw_area, 0, 1, 0, 1);
344 attach(scr_time_scrub, 0, 1, 1, 2, Gtk::EXPAND|Gtk::FILL, Gtk::SHRINK);
349 hbox = manage(new Gtk::HBox);
352 IMAGIFY_BUTTON(button,Gtk::Stock::REFRESH,_("Toggle Looping"));
353 hbox->pack_start(b_loop,Gtk::PACK_SHRINK,0);
354 //attach(b_loop,0,1,2,3,Gtk::EXPAND|Gtk::FILL,Gtk::SHRINK);
356 button = manage(new Gtk::Button(/*_("Play")*/));
357 button->signal_clicked().connect(sigc::mem_fun(*this,&Widget_Preview::play));
358 IMAGIFY_BUTTON(button,Gtk::Stock::GO_FORWARD,_("Play"));
359 hbox->pack_start(*button,Gtk::PACK_SHRINK,0);
360 //attach(*button,1,2,2,3,Gtk::EXPAND|Gtk::FILL,Gtk::SHRINK);
362 button = manage(new Gtk::Button(/*_("Stop")*/));
363 button->signal_clicked().connect(sigc::mem_fun(*this,&Widget_Preview::stop));
364 IMAGIFY_BUTTON(button,Gtk::Stock::NO,_("Stop"));
365 hbox->pack_start(*button,Gtk::PACK_SHRINK,0);
366 //attach(*button,2,3,2,3,Gtk::EXPAND|Gtk::FILL,Gtk::SHRINK);
368 //attack the stop render and erase all buttons to same line...
370 Gtk::VSeparator *vsep = manage(new Gtk::VSeparator);
371 hbox->pack_start(*vsep,Gtk::PACK_SHRINK,0);
374 button = manage(new Gtk::Button(/*_("Halt Render")*/));
375 button->signal_clicked().connect(sigc::mem_fun(*this,&Widget_Preview::stoprender));
376 IMAGIFY_BUTTON(button,Gtk::Stock::STOP,_("Halt Render"));
377 hbox->pack_start(*button,Gtk::PACK_SHRINK,0);
378 //attach(*button,2,3,3,4,Gtk::EXPAND|Gtk::FILL,Gtk::SHRINK);
380 button = manage(new Gtk::Button(/*_("Re-Preview")*/));
381 button->signal_clicked().connect(sigc::mem_fun(*this,&Widget_Preview::repreview));
382 IMAGIFY_BUTTON(button,Gtk::Stock::CONVERT,_("Re-Preview"));
383 hbox->pack_start(*button,Gtk::PACK_SHRINK,0);
384 //attach(*button,0,2,4,5,Gtk::EXPAND|Gtk::FILL,Gtk::SHRINK);
386 button = manage(new Gtk::Button(/*_("Erase All")*/));
387 button->signal_clicked().connect(sigc::mem_fun(*this,&Widget_Preview::eraseall));
388 IMAGIFY_BUTTON(button,Gtk::Stock::DELETE,_("Erase All"));
389 hbox->pack_start(*button,Gtk::PACK_SHRINK,0);
390 //attach(*button,2,3,4,5,Gtk::EXPAND|Gtk::FILL,Gtk::SHRINK);
393 attach(*hbox,0,1,2,3,Gtk::EXPAND|Gtk::FILL,Gtk::SHRINK|Gtk::FILL);
396 hbox = manage(new Gtk::HBox);
398 Gtk::Label *label = manage(new Gtk::Label(_("Last Rendered: ")));
400 hbox->pack_start(*label,Gtk::PACK_SHRINK,10);
401 //attach(*manage(new Gtk::Label(_("Last Rendered: "))),0,1,3,4,Gtk::SHRINK,Gtk::SHRINK);
404 hbox->pack_start(l_lasttime,Gtk::PACK_SHRINK,0);
406 attach(*hbox,0,1,3,4,Gtk::EXPAND|Gtk::FILL,Gtk::SHRINK);
407 //attach(l_lasttime,0,1,3,4,Gtk::EXPAND|Gtk::FILL,Gtk::SHRINK);
410 disp_sound.set_size_request(-1,32);
411 attach(disp_sound,0,1,4,5,Gtk::EXPAND|Gtk::FILL,Gtk::SHRINK);
415 //if(draw_area.get_window()) gc_area = Gdk::GC::create(draw_area.get_window());
419 studio::Widget_Preview::~Widget_Preview()
423 void studio::Widget_Preview::update()
425 //the meat goes in this locker...
426 double time = adj_time_scrub.get_value();
428 //find the frame and display it...
431 //synfig::warning("Updating at %.3f s",time);
433 //use time to find closest frame...
434 studio::Preview::FlipBook::const_iterator beg = preview->begin(),end = preview->end();
435 studio::Preview::FlipBook::const_iterator i;
439 //go to current hint if need be...
440 if(currentindex >= 0 && currentindex < (int)preview->numframes())
442 i = beg+currentindex;
445 //we can't have a picture if there are none to get
448 //don't bother with binary search it will just be slower...
450 //synfig::info("Search for time %f",time);
452 //incrementally go in either direction
453 //(bias downward towards beg, because that's what we want)
456 //synfig::info("Look at %f",i->t);
457 if(i->t > time) break;
458 //synfig::info("Go past...");
463 //bias down, so we can't be at end... and it still is valid...
467 //synfig::info("Look at %f",i->t);
468 if(i->t <= time) break;
469 //synfig::info("Go past...");
472 /*i = preview->begin(); end = preview->end();
476 for(;i != end; j = i++)
478 if(i->t > time) break;
481 //we should be at a valid edge since we biased downward
483 //don't get the closest, round down... (if we can)
486 synfig::error("i == end....");
494 currentindex = i-beg;
498 //synfig::warning("Update at: %f seconds (%f s)",time,timedisp);
500 //synfig::warning("success!");
506 if(disp_sound.get_profile() && adj_sound.get_value() != time)
510 //Set the position of the sound (short circuited for sound modifying the time)
512 disp_sound.set_position(time);
513 disp_sound.queue_draw();
516 void studio::Widget_Preview::preview_draw()
518 draw_area.queue_draw();//on_expose_event();
521 bool studio::Widget_Preview::redraw(GdkEventExpose */*heh*/)
523 //And render the drawing area
524 Glib::RefPtr<Gdk::Pixbuf> pxnew, px = currentbuf;
526 if(!px || draw_area.get_height() == 0
527 || px->get_height() == 0 || px->get_width() == 0 /*|| is_visible()*/) //made not need this line
530 //figure out the scaling factors...
534 sx = draw_area.get_width() / (float)px->get_width();
535 sy = draw_area.get_height() / (float)px->get_height();
537 //synfig::info("widget_preview redraw: now to scale the bitmap: %.3f x %.3f",sx,sy);
539 //round to smallest scale (fit entire thing in window without distortion)
543 //scale to a new pixmap and then copy over to the window
544 nw = (int)(px->get_width()*sx);
545 nh = (int)(px->get_height()*sx);
547 if(nw == 0 || nh == 0)return true;
549 pxnew = px->scale_simple(nw,nh,Gdk::INTERP_NEAREST);
551 //synfig::info("Now to draw to the window...");
553 Glib::RefPtr<Gdk::Window> wind = draw_area.get_window();
554 Glib::RefPtr<Gdk::Drawable> surf = Glib::RefPtr<Gdk::Drawable>::cast_static(wind);
555 Glib::RefPtr<Gdk::GC> gc = Gdk::GC::create(wind);
558 Gdk::Rectangle r(0,0,draw_area.get_width(),draw_area.get_height());
559 draw_area.get_window()->begin_paint_rect(r);
562 if(!wind) synfig::warning("The destination window is broken...");
563 if(!surf) synfig::warning("The destination is not drawable...");
567 /* Options for drawing...
568 1) store with alpha, then clear and render with alpha every frame
569 - more time consuming
571 2) store with just pixel info
574 + better memory footprint
576 //px->composite(const Glib::RefPtr<Gdk::Pixbuf>& dest, int dest_x, int dest_y, int dest_width, int dest_height, double offset_x, double offset_y, double scale_x, double scale_y, InterpType interp_type, int overall_alpha) const
581 0, 0, // Source X and Y
582 0, 0, // Dest X and Y
583 -1,-1, // Width and Height
584 Gdk::RGB_DITHER_NONE, // RgbDither
585 0, 0 // Dither offset X and Y
590 Glib::RefPtr<Pango::Layout> layout(Pango::Layout::create(get_pango_context()));
591 Glib::ustring timecode(Time((double)timedisp).round(preview->get_global_fps())
592 .get_string(preview->get_global_fps(),
593 App::get_time_format()));
594 //synfig::info("Time for preview draw is: %s for time %g", timecode.c_str(), adj_time_scrub.get_value());
596 gc->set_rgb_fg_color(Gdk::Color("#FF0000"));
597 layout->set_text(timecode);
598 surf->draw_layout(gc,4,4,layout);
602 draw_area.get_window()->end_paint();
604 //synfig::warning("Refresh the draw area");
605 //make sure the widget refreshes
610 bool studio::Widget_Preview::play_update()
612 float diff = timer.pop_time();
613 //synfig::info("Play update: diff = %.2f",diff);
617 //we go to the next one...
618 double time = adj_time_scrub.get_value() + diff;
620 //adjust it to be synced with the audio if it can...
622 double newtime = audiotime;
623 if(audio && audio->is_playing()) audio->get_current_time(newtime);
625 if(newtime != audiotime)
627 //synfig::info("Adjusted time from %.3lf to %.3lf", time,newtime);
628 time = audiotime = newtime;
632 //Looping conditions...
633 if(time >= adj_time_scrub.get_upper())
637 time = adj_time_scrub.get_lower();// + time-adj_time_scrub.get_upper();
641 time = adj_time_scrub.get_upper();
642 adj_time_scrub.set_value(time);
646 //synfig::info("Play Stopped: time set to %f",adj_time_scrub.get_value());
651 //set the new time...
652 adj_time_scrub.set_value(time);
653 adj_time_scrub.value_changed();
655 //update the window to the correct image we might want to do this later...
657 //synfig::warning("Did update pu");
662 void studio::Widget_Preview::slider_move()
667 //synfig::warning("Did update sm");
671 //for other things updating the value changed signal...
672 void studio::Widget_Preview::scrub_updated(double t)
676 //Attempt at being more accurate... the time is adjusted to be exactly where the sound says it is
680 if(!audio->isPaused())
682 audio->get_current_time(t);
686 //synfig::info("Scrubbing to %.3f, setting adj to %.3f",oldt,t);
688 if(adj_time_scrub.get_value() != t)
690 adj_time_scrub.set_value(t);
691 adj_time_scrub.value_changed();
695 void studio::Widget_Preview::disconnect_preview(Preview *prev)
700 prevchanged.disconnect();
704 void studio::Widget_Preview::set_preview(etl::handle<Preview> prev)
708 synfig::info("Setting preview");
710 //stop playing the mini animation...
715 //set the internal values
716 float rate = preview->get_fps();
717 synfig::info(" FPS = %f",rate);
720 float start = preview->get_begintime();
721 float end = preview->get_endtime();
725 adj_time_scrub.set_lower(start);
726 adj_time_scrub.set_upper(end);
727 adj_time_scrub.set_value(start);
728 adj_time_scrub.set_step_increment(rate);
729 adj_time_scrub.set_page_increment(10*rate);
731 //if the begin time and the end time are the same there is only a single frame
732 singleframe = end==start;
735 adj_time_scrub.set_lower(0);
736 adj_time_scrub.set_upper(0);
737 adj_time_scrub.set_value(0);
738 adj_time_scrub.set_step_increment(0);
739 adj_time_scrub.set_page_increment(0);
743 //connect so future information will be found...
744 prevchanged = prev->signal_changed().connect(sigc::mem_fun(*this,&Widget_Preview::whenupdated));
745 prev->signal_destroyed().connect(sigc::mem_fun(*this,&Widget_Preview::disconnect_preview));
747 //synfig::warning("Did update sp");
752 void studio::Widget_Preview::whenupdated()
754 l_lasttime.set_text((Time((double)(--preview->end())->t)
755 .round(preview->get_global_fps())
756 .get_string(preview->get_global_fps(),App::get_time_format())));
760 void studio::Widget_Preview::clear()
763 prevchanged.disconnect();
766 void studio::Widget_Preview::play()
768 if(preview && !playing)
770 //synfig::info("Playing at %lf",adj_time_scrub.get_value());
771 //audiotime = adj_time_scrub.get_value();
774 //adj_time_scrub.set_value(adj_time_scrub.get_lower());
775 update(); //we don't want to call play update because that will try to advance the timer
776 //synfig::warning("Did update p");
778 //approximate length of time in seconds, right?
779 double rate = /*std::min(*/adj_time_scrub.get_step_increment()/*,1/30.0)*/;
780 int timeout = (int)floor(1000*rate);
782 //synfig::info(" rate = %.3lfs = %d ms",rate,timeout);
784 signal_play_(adj_time_scrub.get_value());
787 if(audio) audio->play(adj_time_scrub.get_value());
789 timecon = Glib::signal_timeout().connect(sigc::mem_fun(*this,&Widget_Preview::play_update),timeout);
795 void studio::Widget_Preview::play_stop()
799 if(audio) audio->stop(); //!< stop the audio
800 //synfig::info("Stopping...");
803 void studio::Widget_Preview::stop()
805 //synfig::warning("stopping");
807 timecon.disconnect();
810 bool studio::Widget_Preview::scroll_move_event(GdkEvent *event)
814 case GDK_BUTTON_PRESS:
816 if(event->button.button == 1 || event->button.button == 3)
828 void studio::Widget_Preview::set_audioprofile(etl::handle<AudioProfile> p)
830 disp_sound.set_profile(p);
833 void studio::Widget_Preview::set_audio(etl::handle<AudioContainer> a)
837 //disconnect any previous signals
838 scrstartcon.disconnect(); scrstopcon.disconnect(); scrubcon.disconnect();
840 //connect the new signals
841 scrstartcon = disp_sound.signal_start_scrubbing().connect(sigc::mem_fun(*a,&AudioContainer::start_scrubbing));
842 scrstopcon = disp_sound.signal_stop_scrubbing().connect(sigc::mem_fun(*a,&AudioContainer::stop_scrubbing));
843 scrubcon = disp_sound.signal_scrub().connect(sigc::mem_fun(*a,&AudioContainer::scrub));
846 void studio::Widget_Preview::seek(float t)
849 adj_time_scrub.set_value(t);
852 void studio::Widget_Preview::repreview()
858 preview->get_canvasview()->preview_option();
862 void studio::Widget_Preview::stoprender()
866 // don't crash if the render has already been stopped
867 if (!preview->renderer)
870 if (preview->renderer->updating)
871 preview->renderer->stop();
873 preview->renderer.detach();
877 void studio::Widget_Preview::eraseall()