1 /*
2  *   Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012
3  *   Free Software Foundation, Inc.
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
18  *
19  *
20  */
21 
22 #include "MovieTester.h"
23 #include "GnashException.h"
24 #include "URL.h"
25 #include "noseek_fd_adapter.h"
26 #include "movie_definition.h"
27 #include "Movie.h"
28 #include "movie_root.h"
29 #include "MovieClip.h"
30 #include "MovieFactory.h"
31 #include "sound_handler.h" // for creating the "test" sound handlers
32 #include "NullSoundHandler.h"
33 #include "RGBA.h" // for rgba class (pixel checking)
34 #include "FuzzyPixel.h" // for pixel checking
35 #include "Renderer.h"
36 #include "ManualClock.h" // for use by advance
37 #include "StreamProvider.h" // for passing to RunResources
38 #include "IOChannel.h"
39 #include "swf/TagLoadersTable.h"
40 #include "swf/DefaultTagLoaders.h"
41 #include "GnashFactory.h"
42 
43 #ifdef RENDERER_CAIRO
44 # include "Renderer_cairo.h"
45 #endif
46 #ifdef RENDERER_OPENGL
47 # include "Renderer_ogl.h"
48 #endif
49 #ifdef RENDERER_AGG
50 # include "Renderer_agg.h"
51 #endif
52 
53 #include "MediaHandler.h"
54 
55 #include <cstdio>
56 #include <string>
57 #include <memory> // for unique_ptr
58 #include <cmath> // for ceil
59 #include <iostream>
60 
61 //#define SHOW_INVALIDATED_BOUNDS_ON_ADVANCE 1
62 
63 #ifdef SHOW_INVALIDATED_BOUNDS_ON_ADVANCE
64 #include <sstream>
65 #endif
66 
67 
68 using std::cout;
69 using std::endl;
70 
71 namespace gnash {
72 
73 namespace {
74     // exp2 isn't part of standard C++, so is defined here in case the compiler
75     // doesn't supply it (e.g. in BSD)
exp2(double x)76     inline double exp2(double x) { return std::pow(2.0, x); }
77 
78     bool getAveragePixel(const Renderer& r, rgba& color_return, int x, int y,
79         unsigned int radius);
80 
toShortString(const rgba & r)81     std::string toShortString(const rgba& r) {
82         std::stringstream ss;
83         ss << +r.m_r << "," << +r.m_g << "," << +r.m_b << "," << +r.m_a;
84         return ss.str();
85     }
86 }
87 
MovieTester(const std::string & url)88 MovieTester::MovieTester(const std::string& url)
89     :
90     _x(0),
91     _y(0),
92     _forceRedraw(true),
93     _samplesFetched(0)
94 {
95 
96 #ifdef USE_MEDIA
97     // Initialize the testing media handlers
98     initTestingMediaHandlers();
99 #endif
100 
101 #ifdef USE_SOUND
102     // Initialize the sound handler(s)
103     initTestingSoundHandlers();
104     _runResources.setSoundHandler(_sound_handler);
105 #endif
106 #ifdef USE_MEDIA
107     _runResources.setMediaHandler(_mediaHandler);
108 #endif
109 
110     std::shared_ptr<SWF::TagLoadersTable> loaders(new SWF::TagLoadersTable());
111     addDefaultLoaders(*loaders);
112 
113     _runResources.setTagLoaders(loaders);
114 
115     std::shared_ptr<StreamProvider> sp(new StreamProvider(url, url));
116 
117     _runResources.setStreamProvider(sp);
118 
119     if ( url == "-" ) {
120 	std::unique_ptr<IOChannel> in (
121 		noseek_fd_adapter::make_stream(fileno(stdin))
122 				     );
123 		_movie_def = MovieFactory::makeMovie(std::move(in), url, _runResources, false);
124 	} else {
125 	URL urlObj(url);
126 	if ( urlObj.protocol() == "file" ) {
127 	    RcInitFile& rcfile = RcInitFile::getDefaultInstance();
128 	    const std::string& path = urlObj.path();
129 #if 1 // add the *directory* the movie was loaded from to the local sandbox path
130 	    size_t lastSlash = path.find_last_of('/');
131 	    std::string dir = path.substr(0, lastSlash+1);
132 	    rcfile.addLocalSandboxPath(dir);
133 	    log_debug(_("%s appended to local sandboxes"), dir.c_str());
134 #else // add the *file* to be loaded to the local sandbox path
135 	    rcfile.addLocalSandboxPath(path);
136 	    log_debug(_("%s appended to local sandboxes"), path.c_str());
137 #endif
138 	}
139 	// _url should be always set at this point...
140 	_movie_def = MovieFactory::makeMovie(urlObj, _runResources,
141 					     NULL, false);
142     }
143 
144     if ( ! _movie_def )	{
145 	throw GnashException("Could not load movie from "+url);
146     }
147 
148     _movie_root.reset(new movie_root(_clock, _runResources));
149 
150     // Initialize viewport size with the one advertised in the header
151     _width = unsigned(_movie_def->get_width_pixels());
152     _height = unsigned(_movie_def->get_height_pixels());
153 
154     // Initialize the testing renderers
155     initTestingRenderers();
156 
157     // Now complete load of the movie
158     _movie_def->completeLoad();
159     _movie_def->ensure_frame_loaded(_movie_def->get_frame_count());
160 
161     // Activate verbosity so that self-contained testcases are
162     // also used
163     gnash::LogFile& dbglogfile = gnash::LogFile::getDefaultInstance();
164     dbglogfile.setVerbosity(1);
165 
166     // Finally, place the root movie on the stage ...
167     MovieClip::MovieVariables v;
168     _movie_root->init(_movie_def.get(), v);
169 
170     // ... and render it
171     render();
172 }
173 
~MovieTester()174 MovieTester::~MovieTester()
175 {
176     MovieFactory::clear();
177 }
178 
179 void
render(std::shared_ptr<Renderer> h,InvalidatedRanges & invalidated_regions)180 MovieTester::render(std::shared_ptr<Renderer> h,
181 		    InvalidatedRanges& invalidated_regions)
182 {
183 
184     // This is a bit dangerous, as there isn't really support for swapping
185     // renderers during runtime; though the only problem is likely to be
186     // that CachedBitmaps are missing.
187     _runResources.setRenderer(h);
188 
189     h->set_invalidated_regions(invalidated_regions);
190 
191     // We call display here to simulate effect of a real run.
192     //
193     // What we're particularly interested about is
194     // proper computation of invalidated bounds, which
195     // needs clear_invalidated() to be called.
196     // display() will call clear_invalidated() on DisplayObjects
197     // actually modified so we're fine with that.
198     //
199     // Directly calling _movie->clear_invalidated() here
200     // also work currently, as invalidating the topmost
201     // movie will force recomputation of all invalidated
202     // bounds. Still, possible future changes might
203     // introduce differences, so better to reproduce
204     // real runs as close as possible, by calling display().
205     //
206     _movie_root->display();
207 }
208 
209 void
redraw()210 MovieTester::redraw()
211 {
212     _forceRedraw=true;
213     render();
214 }
215 
216 void
render()217 MovieTester::render()
218 {
219     // Get invalidated ranges and cache them
220     _invalidatedBounds.setNull();
221 
222     _movie_root->add_invalidated_bounds(_invalidatedBounds, false);
223 
224 #ifdef SHOW_INVALIDATED_BOUNDS_ON_ADVANCE
225     const MovieClip* r = getRootMovie();
226     std::cout << "frame " << r->get_current_frame() << ") Invalidated bounds " << _invalidatedBounds << std::endl;
227 #endif
228 
229     // Force full redraw by using a WORLD invalidated ranges
230     InvalidatedRanges ranges = _invalidatedBounds;
231     if ( _forceRedraw )	{
232 	ranges.setWorld(); // set to world if asked a full redraw
233 	_forceRedraw = false; // reset to no forced redraw
234     }
235 
236     for (TestingRenderers::const_iterator it=_testingRenderers.begin(),
237 	     itE=_testingRenderers.end(); it != itE; ++it) {
238 	const TestingRenderer& rend = *it;
239 	render(rend.getRenderer(), ranges);
240     }
241 
242     if ( _testingRenderers.empty() ) {
243 	// Make sure display is called in any case
244 	//
245 	// What we're particularly interested about is
246 	// proper computation of invalidated bounds, which
247 	// needs clear_invalidated() to be called.
248 	// display() will call clear_invalidated() on DisplayObjects
249 	// actually modified so we're fine with that.
250 	//
251 	// Directly calling _movie->clear_invalidated() here
252 	// also work currently, as invalidating the topmost
253 	// movie will force recomputation of all invalidated
254 	// bounds. Still, possible future changes might
255 	// introduce differences, so better to reproduce
256 	// real runs as close as possible, by calling display().
257 	//
258 	_movie_root->display();
259     }
260 }
261 
262 void
advanceClock(unsigned long ms_current)263 MovieTester::advanceClock(unsigned long ms_current)
264 {
265     _clock.advance(ms_current);
266 
267     if ( _sound_handler ) {
268 
269         unsigned int ms = _clock.elapsed();
270 
271         // We need to fetch as many samples
272         // as needed for a theoretical 44100hz loop.
273         // That is 44100 samples each second.
274         // 44100/1000 = x/ms
275         //  x = (44100*ms) / 1000
276         unsigned int nSamples = (441*ms) / 10;
277 
278         // We double because sound_handler interface takes
279         // "mono" samples... (eh.. would be wise to change)
280         unsigned int toFetch = nSamples*2;
281 
282         // Now substract what we fetched already
283         toFetch -= _samplesFetched;
284 
285         // And update _samplesFetched..
286         _samplesFetched += toFetch;
287 
288         log_debug("advanceClock(%d) needs to fetch %d samples", ms, toFetch);
289 
290         std::int16_t samples[1024];
291         while (toFetch) {
292             unsigned int n = std::min(toFetch, 1024u);
293             _sound_handler->fetchSamples((std::int16_t*)&samples, n);
294             toFetch -= n;
295         }
296     }
297 }
298 
299 void
advance(bool updateClock)300 MovieTester::advance(bool updateClock)
301 {
302     if ( updateClock ) {
303         // TODO: cache 'clockAdvance'
304         float fps = _movie_def->get_frame_rate();
305         unsigned long clockAdvance = long(1000/fps);
306         advanceClock(clockAdvance);
307     }
308 
309     if (_movie_root->advance()) render();
310 
311 }
312 
313 void
resizeStage(int x,int y)314 MovieTester::resizeStage(int x, int y)
315 {
316     _movie_root->setDimensions(x, y);
317 
318     if (_movie_root->getStageScaleMode() != movie_root::SCALEMODE_NOSCALE) {
319 	// TODO: fix to deal with all scale modes
320 	//       and alignments ?
321 
322 	// set new scale value
323 	float xscale = x / _movie_def->get_width_pixels();
324 	float yscale = y / _movie_def->get_height_pixels();
325 
326 	if (xscale < yscale) yscale = xscale;
327 	if (yscale < xscale) xscale = yscale;
328 
329         // Scale for all renderers.
330         for (TestingRenderers::iterator it=_testingRenderers.begin(),
331 		 itE=_testingRenderers.end(); it != itE; ++it) {
332             TestingRenderer& rend = *it;
333             Renderer* h = rend.getRenderer().get();
334             h->set_scale(xscale, yscale);
335         }
336     }
337 }
338 
339 const DisplayObject*
findDisplayItemByName(const MovieClip & mc,const std::string & name)340 MovieTester::findDisplayItemByName(const MovieClip& mc,
341 		const std::string& name)
342 {
343     const DisplayList& dlist = mc.getDisplayList();
344     string_table& st = getStringTable(*getObject(&mc));
345     VM& vm = getVM(*getObject(&mc));
346     return dlist.getDisplayObjectByName(st, getURI(vm, name), false);
347 }
348 
349 const DisplayObject*
findDisplayItemByTarget(const std::string & tgt)350 MovieTester::findDisplayItemByTarget(const std::string& tgt)
351 {
352     return _movie_root->findCharacterByTarget(tgt);
353 }
354 
355 const DisplayObject*
findDisplayItemByDepth(const MovieClip & mc,int depth)356 MovieTester::findDisplayItemByDepth(const MovieClip& mc,
357 		int depth)
358 {
359     const DisplayList& dlist = mc.getDisplayList();
360     return dlist.getDisplayObjectAtDepth(depth);
361 }
362 
363 void
movePointerTo(int x,int y)364 MovieTester::movePointerTo(int x, int y)
365 {
366     _x = x;
367     _y = y;
368     if ( _movie_root->mouseMoved(x, y) ) render();
369 }
370 
371 void
checkPixel(int x,int y,unsigned radius,const rgba & color,short unsigned tolerance,const std::string & label,bool expectFailure) const372 MovieTester::checkPixel(int x, int y, unsigned radius, const rgba& color,
373 		short unsigned tolerance, const std::string& label, bool expectFailure) const
374 {
375     if ( ! canTestRendering() )	{
376 	std::stringstream ss;
377 	ss << "exp:" << toShortString(color) << " ";
378 	cout << "UNTESTED: NORENDERER: pix:" << x << "," << y << " exp:" <<
379         toShortString(color) << " " << label << endl;
380     }
381 
382     FuzzyPixel exp(color, tolerance);
383     const char* X="";
384     if ( expectFailure ) X="X";
385 
386     //std::cout <<"chekPixel(" << color << ") called" << std::endl;
387 
388     for (TestingRenderers::const_iterator it=_testingRenderers.begin(),
389 	     itE=_testingRenderers.end(); it != itE; ++it) {
390 	const TestingRenderer& rend = *it;
391 
392 	std::stringstream ss;
393 	ss << rend.getName() <<" ";
394 	ss << "pix:" << x << "," << y <<" ";
395 
396 	rgba obt_col;
397 
398 	const Renderer& handler = *rend.getRenderer();
399 
400 	if (!getAveragePixel(handler, obt_col, x, y, radius) ) {
401 	    ss << " is out of rendering buffer";
402 	    cout << X << "FAILED: " << ss.str() << " (" << label << ")" << endl;
403 	    continue;
404 	}
405 
406 	// Find minimum tolerance as a function of BPP
407 
408 	unsigned short minRendererTolerance = 1;
409 	unsigned int bpp = handler.getBitsPerPixel();
410 	if ( bpp ) {
411 	    // UdoG: check_pixel should *always* tolerate at least 2 ^ (8 - bpp/3)
412 	    minRendererTolerance = int(std::ceil(exp2(8 - bpp/3)));
413 	}
414 
415 	//unsigned short tol = std::max(tolerance, minRendererTolerance);
416 	unsigned short tol = tolerance*minRendererTolerance;
417 
418 	ss << "exp:" << toShortString(color) << " ";
419 	ss << "obt:" << toShortString(obt_col) << " ";
420 	ss << "tol:" << tol;
421 
422 	FuzzyPixel obt(obt_col, tol);
423 	// equality operator would use tolerance of most tolerating FuzzyPixel
424 	if (exp ==  obt) {
425 	    cout << X << "PASSED: " << ss.str() << " (" << label << ")" << endl;
426 	} else {
427 	    cout << X << "FAILED: " << ss.str() << " (" << label << ")" << endl;
428 	}
429     }
430 }
431 
432 void
pressMouseButton()433 MovieTester::pressMouseButton()
434 {
435     if ( _movie_root->mouseClick(true) ) {
436 	render();
437     }
438 }
439 
440 void
depressMouseButton()441 MovieTester::depressMouseButton()
442 {
443     if ( _movie_root->mouseClick(false) ) {
444 	render();
445     }
446 }
447 
448 void
click()449 MovieTester::click()
450 {
451     int wantRedraw = 0;
452     if ( _movie_root->mouseClick(true) ) ++wantRedraw;
453     if ( _movie_root->mouseClick(false) ) ++wantRedraw;
454 
455     if ( wantRedraw ) render();
456 }
457 
458 void
scrollMouse(int delta)459 MovieTester::scrollMouse(int delta)
460 {
461     if (_movie_root->mouseWheel(delta)) render();
462 }
463 
464 void
pressKey(key::code code)465 MovieTester::pressKey(key::code code)
466 {
467     if ( _movie_root->keyEvent(code, true) ) {
468 	render();
469     }
470 }
471 
472 void
releaseKey(key::code code)473 MovieTester::releaseKey(key::code code)
474 {
475     if ( _movie_root->keyEvent(code, false) ) {
476 	render();
477     }
478 }
479 
480 bool
isMouseOverMouseEntity()481 MovieTester::isMouseOverMouseEntity()
482 {
483     return (_movie_root->getActiveEntityUnderPointer());
484 }
485 
486 bool
usingHandCursor()487 MovieTester::usingHandCursor()
488 {
489 	DisplayObject* activeEntity = _movie_root->getActiveEntityUnderPointer();
490 	if ( ! activeEntity ) return false;
491 
492     if ( activeEntity->isSelectableTextField() ) {
493         return false; // setCursor(CURSOR_INPUT);
494     } else if ( activeEntity->allowHandCursor() ) {
495         return true; // setCursor(CURSOR_HAND);
496     } else {
497         return false; // setCursor(CURSOR_NORMAL);
498     }
499 }
500 
501 geometry::SnappingRanges2d<int>
getInvalidatedRanges() const502 MovieTester::getInvalidatedRanges() const
503 {
504     using namespace gnash::geometry;
505 
506     SnappingRanges2d<float> ranges = _invalidatedBounds;
507 
508     // scale by 1/20 (twips to pixels)
509     ranges.scale(1.0/20);
510 
511     // Convert to integer range.
512     SnappingRanges2d<int> pixranges(ranges);
513 
514     return pixranges;
515 
516 }
517 
518 bool
streamingSound() const519 MovieTester::streamingSound() const
520 {
521     if (!_sound_handler.get()) return false;
522 #ifdef USE_SOUND
523     return _sound_handler->streamingSound();
524 #endif
525 }
526 
527 int
soundsStarted()528 MovieTester::soundsStarted()
529 {
530     if ( ! _sound_handler.get() ) return 0;
531 #ifdef USE_SOUND
532     return _sound_handler->numSoundsStarted();
533 #endif
534 }
535 
536 int
soundsStopped()537 MovieTester::soundsStopped()
538 {
539     if ( ! _sound_handler.get() ) return 0;
540     return _sound_handler->numSoundsStopped();
541 }
542 
543 void
initTestingRenderers()544 MovieTester::initTestingRenderers()
545 {
546     std::shared_ptr<Renderer> handler;
547 
548     // TODO: add support for testing multiple renderers
549     // This is tricky as requires changes in the core lib
550 
551 #ifdef RENDERER_AGG
552     // Initialize AGG
553     static const char* aggPixelFormats[] = {
554 	"RGB555", "RGB565", "RGBA16",
555 	"RGB24", "BGR24", "RGBA32", "BGRA32",
556 	"ARGB32", "ABGR32"
557     };
558 
559     for (unsigned i=0; i<sizeof(aggPixelFormats)/sizeof(*aggPixelFormats); ++i)	{
560 	const char* pixelFormat = aggPixelFormats[i];
561 	std::string name = "AGG_" + std::string(pixelFormat);
562 
563 	handler.reset( create_Renderer_agg(pixelFormat) );
564 	if ( handler.get() ) {
565 	    //log_debug("Renderer %s initialized", name.c_str());
566 	    std::cout << "Renderer " << name << " initialized" << std::endl;
567 	    addTestingRenderer(handler, name);
568 	} else {
569 	    std::cout << "Renderer " << name << " not supported" << std::endl;
570 	}
571     }
572 #endif // RENDERER_AGG
573 
574 #ifdef RENDERER_CAIRO
575     // Initialize Cairo
576     handler.reset(renderer::cairo::create_handler());
577 
578     addTestingRenderer(handler, "Cairo");
579 #endif
580 
581 #ifdef RENDERER_OPENGL
582     // Initialize opengl renderer
583     handler.reset(renderer::opengl::create_handler(false));
584     addTestingRenderer(handler, "OpenGL");
585 #endif
586 }
587 
588 void
addTestingRenderer(std::shared_ptr<Renderer> h,const std::string & name)589 MovieTester::addTestingRenderer(std::shared_ptr<Renderer> h,
590         const std::string& name)
591 {
592     if ( ! h->initTestBuffer(_width, _height) )	{
593 	std::cout << "UNTESTED: render handler " << name
594 		  << " doesn't support in-memory rendering "
595 		  << std::endl;
596 	return;
597     }
598 
599     // TODO: make the core lib support this
600     if ( ! _testingRenderers.empty() ) {
601 	std::cout << "UNTESTED: can't test render handler " << name
602 		  << " because gnash core lib is unable to support testing of "
603 		  << "multiple renderers from a single process "
604 		  << "and we're already testing render handler "
605 		  << _testingRenderers.front().getName()
606 		  << std::endl;
607 	return;
608     }
609 
610     _testingRenderers.push_back(TestingRenderer(h, name));
611 
612     // this will be needed till we allow run-time swapping of renderers,
613     // see above UNTESTED message...
614     _runResources.setRenderer(_testingRenderers.back().getRenderer());
615 }
616 
617 bool
canTestVideo() const618 MovieTester::canTestVideo() const
619 {
620     if ( ! canTestSound() ) return false;
621 
622     return true;
623 }
624 
625 void
initTestingSoundHandlers()626 MovieTester::initTestingSoundHandlers()
627 {
628 #ifdef USE_SOUND
629     // Currently, SoundHandler can't be constructed
630     // w/out a registered MediaHandler .
631     // Should be fixed though...
632     if (_mediaHandler.get()) {
633         _sound_handler.reset(new sound::NullSoundHandler(_mediaHandler.get()));
634     } else {
635         log_error("No media handler available, "
636             "could not construct sound handler");
637     }
638 #endif  // USE_SOUND
639 }
640 
641 void
initTestingMediaHandlers()642 MovieTester::initTestingMediaHandlers()
643 {
644 #ifdef USE_MEDIA
645     // TODO: allow selection.
646     _mediaHandler.reset(media::MediaFactory::instance().get(""));
647 #endif
648 }
649 
650 void
restart()651 MovieTester::restart()
652 {
653     _movie_root->reset();
654     MovieClip::MovieVariables v;
655     _movie_root->init(_movie_def.get(), v);
656 
657     // Set _movie before calling ::render
658     render();
659 }
660 
661 float
getFrameRate() const662 MovieTester::getFrameRate() const
663 {
664     return _movie_def->get_frame_rate();
665 }
666 
667 namespace {
668 
669 /// Returns the average RGB color for a square block on the stage. The
670 /// width and height of the block is defined by "radius" and x/y refer
671 /// to the center of the block. radius==1 equals getPixel() and radius==0
672 /// is illegal. For even "radius" values, the center point is not exactly
673 /// defined.
674 /// The function returns false when at least one pixel of the block was
675 /// outside the main frame buffer. In that case the value in color_return
676 /// is undefined.
getAveragePixel(const Renderer & rh,rgba & color_return,int x,int y,unsigned int radius)677 bool getAveragePixel(const Renderer& rh, rgba& color_return, int x, int y,
678     unsigned int radius)
679 {
680     assert(radius>0);
681 
682     // optimization:
683     if (radius==1) return rh.getPixel(color_return, x, y);
684 
685     unsigned int r=0, g=0, b=0, a=0;
686 
687     x -= radius/2;
688     y -= radius/2;
689 
690     int xe = x+radius;
691     int ye = y+radius;
692 
693     rgba pixel;
694 
695     for (int yp=y; yp<ye; yp++)
696     for (int xp=x; xp<xe; xp++) {
697         if (!rh.getPixel(pixel, xp, yp))
698             return false;
699 
700         r += pixel.m_r;
701         g += pixel.m_g;
702         b += pixel.m_b;
703         a += pixel.m_a;
704     }
705 
706     int pcount = radius*radius;
707     color_return.m_r = r / pcount;
708     color_return.m_g = g / pcount;
709     color_return.m_b = b / pcount;
710     color_return.m_a = a / pcount;
711 
712     return true;
713 }
714 
715 }
716 
717 } // namespace gnash
718