1 /**************************************************************
2  *
3  * Licensed to the Apache Software Foundation (ASF) under one
4  * or more contributor license agreements.  See the NOTICE file
5  * distributed with this work for additional information
6  * regarding copyright ownership.  The ASF licenses this file
7  * to you under the Apache License, Version 2.0 (the
8  * "License"); you may not use this file except in compliance
9  * with the License.  You may obtain a copy of the License at
10  *
11  *   http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing,
14  * software distributed under the License is distributed on an
15  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16  * KIND, either express or implied.  See the License for the
17  * specific language governing permissions and limitations
18  * under the License.
19  *
20  *************************************************************/
21 
22 
23 
24 // MARKER(update_precomp.py): autogen include statement, do not remove
25 #include "precompiled_canvas.hxx"
26 
27 #include <canvas/debug.hxx>
28 #include <tools/diagnose_ex.h>
29 #include <canvas/verbosetrace.hxx>
30 #include <canvas/canvastools.hxx>
31 
32 #include <vcl/canvastools.hxx>
33 #include <vcl/outdev.hxx>
34 #include <vcl/window.hxx>
35 #include <vcl/bitmapex.hxx>
36 
37 #include <basegfx/range/b2drectangle.hxx>
38 #include <basegfx/tools/canvastools.hxx>
39 
40 #include <boost/cast.hpp>
41 
42 #include "spritecanvashelper.hxx"
43 #include "canvascustomsprite.hxx"
44 
45 
46 using namespace ::com::sun::star;
47 
48 #define FPS_BOUNDS Rectangle(0,0,130,90)
49 #define INFO_COLOR COL_RED
50 
51 namespace vclcanvas
52 {
53     namespace
54     {
55         /** Sprite redraw at original position
56 
57             Used to repaint the whole canvas (background and all
58             sprites)
59          */
60         void spriteRedraw( OutputDevice&                      rOutDev,
61                            const ::canvas::Sprite::Reference& rSprite )
62         {
63             // downcast to derived vclcanvas::Sprite interface, which
64             // provides the actual redraw methods.
65             ::boost::polymorphic_downcast< Sprite* >(rSprite.get())->redraw(rOutDev,
66                                                                             true);
67         }
68 
69         double calcNumPixel( const ::canvas::Sprite::Reference&	rSprite )
70         {
71             const ::basegfx::B2DSize& rSize(
72                 ::boost::polymorphic_downcast< Sprite* >(rSprite.get())->getSizePixel() );
73 
74             return rSize.getX() * rSize.getY();
75         }
76 
77         void repaintBackground( OutputDevice& 				rOutDev,
78                                 OutputDevice& 				rBackBuffer,
79                                 const ::basegfx::B2DRange&	rArea )
80         {
81             const ::Point& rPos( ::vcl::unotools::pointFromB2DPoint( rArea.getMinimum()) );
82             const ::Size& rSize( ::vcl::unotools::sizeFromB2DSize( rArea.getRange()) );
83 
84             rOutDev.DrawOutDev( rPos, rSize, rPos, rSize, rBackBuffer );
85         }
86 
87         void opaqueUpdateSpriteArea( const ::canvas::Sprite::Reference& rSprite,
88                                      OutputDevice&                      rOutDev,
89                                      const ::basegfx::B2IRange&         rArea )
90         {
91             const Rectangle& rRequestedArea(
92                 ::vcl::unotools::rectangleFromB2IRectangle( rArea ) );
93 
94             // clip output to actual update region (otherwise a)
95             // wouldn't save much render time, and b) will clutter
96             // scrolled sprite content outside this area)
97             rOutDev.EnableMapMode( sal_False );
98             rOutDev.SetClipRegion( rRequestedArea );
99 
100             // repaint affected sprite directly to output device (at
101             // the actual screen output position)
102             ::boost::polymorphic_downcast< Sprite* >(
103                 rSprite.get() )->redraw( rOutDev,
104                                          false ); // rendering
105                                                   // directly to
106                                                   // frontbuffer
107         }
108 
109         /** Repaint sprite at original position
110 
111             Used for opaque updates, which render directly to the
112             front buffer.
113          */
114         void spriteRedrawStub( OutputDevice&                      rOutDev,
115                                const ::canvas::Sprite::Reference& rSprite )
116         {
117             if( rSprite.is() )
118             {
119                 ::boost::polymorphic_downcast< Sprite* >(
120                     rSprite.get() )->redraw( rOutDev,
121                                              false );
122             }
123         }
124 
125         /** Repaint sprite at given position
126 
127             Used for generic update, which renders into vdev of
128             adapted size.
129          */
130         void spriteRedrawStub2( OutputDevice&                       rOutDev,
131                                 const ::basegfx::B2DPoint& 			rOutPos,
132                                 const ::canvas::Sprite::Reference&	rSprite )
133         {
134             if( rSprite.is() )
135             {
136                 Sprite* pSprite = ::boost::polymorphic_downcast< Sprite* >(
137                     rSprite.get() );
138 
139 				// calc relative sprite position in rUpdateArea (which
140 				// need not be the whole screen!)
141                 const ::basegfx::B2DPoint& rSpriteScreenPos( pSprite->getPosPixel() );
142                 const ::basegfx::B2DPoint& rSpriteRenderPos( rSpriteScreenPos - rOutPos );
143 
144                 pSprite->redraw( rOutDev, rSpriteRenderPos, true );
145             }
146         }
147 
148         /** Repaint sprite at original position
149 
150             Used for opaque updates from scrollUpdate(), which render
151             directly to the front buffer.
152          */
153         void spriteRedrawStub3( OutputDevice&                                       rOutDev,
154                                 const ::canvas::SpriteRedrawManager::AreaComponent& rComponent )
155         {
156             const ::canvas::Sprite::Reference& rSprite( rComponent.second.getSprite() );
157 
158             if( rSprite.is() )
159             {
160                 ::boost::polymorphic_downcast< Sprite* >(
161                     rSprite.get() )->redraw( rOutDev,
162                                              false );
163             }
164         }
165 
166         void renderInfoText( OutputDevice& 			rOutDev,
167                              const ::rtl::OUString& rStr,
168                              const Point&		    rPos )
169         {
170             Font aVCLFont;
171             aVCLFont.SetHeight( 20 );
172             aVCLFont.SetColor( Color( INFO_COLOR ) );
173 
174             rOutDev.SetTextAlign(ALIGN_TOP);
175             rOutDev.SetTextColor( Color( INFO_COLOR ) );
176             rOutDev.SetFont( aVCLFont );
177 
178             rOutDev.DrawText( rPos, rStr );
179         }
180 
181     }
182 
183     SpriteCanvasHelper::SpriteCanvasHelper() :
184         mpRedrawManager( NULL ),
185         mpOwningSpriteCanvas( NULL ),
186         maVDev(),
187         maLastUpdate(),
188         mbShowFrameInfo( false ),
189         mbShowSpriteBounds( false ),
190         mbIsUnsafeScrolling( false )
191     {
192 #if defined(VERBOSE) && OSL_DEBUG_LEVEL > 0
193         // inverse defaults for verbose debug mode
194         mbShowSpriteBounds = mbShowFrameInfo = true;
195 #endif
196     }
197 
198     void SpriteCanvasHelper::init( const OutDevProviderSharedPtr& rOutDev,
199                                    SpriteCanvas&                  rOwningSpriteCanvas,
200                                    ::canvas::SpriteRedrawManager& rManager,
201                                    bool                           bProtect,
202                                    bool                           bHaveAlpha )
203     {
204         mpOwningSpriteCanvas = &rOwningSpriteCanvas;
205         mpRedrawManager = &rManager;
206 
207         CanvasHelper::init(rOwningSpriteCanvas,rOutDev,bProtect,bHaveAlpha);
208     }
209 
210     void SpriteCanvasHelper::disposing()
211     {
212         mpRedrawManager = NULL;
213         mpOwningSpriteCanvas = NULL;
214 
215         // forward to base
216         CanvasHelper::disposing();
217     }
218 
219     uno::Reference< rendering::XAnimatedSprite > SpriteCanvasHelper::createSpriteFromAnimation(
220         const uno::Reference< rendering::XAnimation >&  )
221     {
222         return uno::Reference< rendering::XAnimatedSprite >();
223     }
224 
225     uno::Reference< rendering::XAnimatedSprite > SpriteCanvasHelper::createSpriteFromBitmaps(
226         const uno::Sequence< uno::Reference< rendering::XBitmap > >& ,
227         sal_Int8                                                      )
228     {
229         return uno::Reference< rendering::XAnimatedSprite >();
230     }
231 
232     uno::Reference< rendering::XCustomSprite > SpriteCanvasHelper::createCustomSprite( const geometry::RealSize2D& spriteSize )
233     {
234         if( !mpRedrawManager || !mpDevice )
235             return uno::Reference< rendering::XCustomSprite >(); // we're disposed
236 
237         return uno::Reference< rendering::XCustomSprite >(
238             new CanvasCustomSprite( spriteSize,
239                                     *mpDevice,
240                                     mpOwningSpriteCanvas,
241                                     mpOwningSpriteCanvas->getFrontBuffer(),
242                                     mbShowSpriteBounds ) );
243     }
244 
245     uno::Reference< rendering::XSprite > SpriteCanvasHelper::createClonedSprite( const uno::Reference< rendering::XSprite >&  )
246     {
247         return uno::Reference< rendering::XSprite >();
248     }
249 
250     sal_Bool SpriteCanvasHelper::updateScreen( sal_Bool bUpdateAll,
251                                                bool&	io_bSurfaceDirty )
252     {
253         if( !mpRedrawManager ||
254             !mpOwningSpriteCanvas ||
255             !mpOwningSpriteCanvas->getFrontBuffer() ||
256             !mpOwningSpriteCanvas->getBackBuffer() )
257         {
258             return sal_False; // disposed, or otherwise dysfunctional
259         }
260 
261         // commit to backbuffer
262         flush();
263 
264         OutputDevice& 		rOutDev( mpOwningSpriteCanvas->getFrontBuffer()->getOutDev() );
265         BackBufferSharedPtr pBackBuffer( mpOwningSpriteCanvas->getBackBuffer() );
266         OutputDevice& 		rBackOutDev( pBackBuffer->getOutDev() );
267 
268         // actual OutputDevice is a shared resource - restore its
269         // state when done.
270         tools::OutDevStateKeeper aStateKeeper( rOutDev );
271 
272         const Size 	aOutDevSize( rBackOutDev.GetOutputSizePixel() );
273         const Point aEmptyPoint(0,0);
274 
275         Window* pTargetWindow = NULL;
276         if( rOutDev.GetOutDevType() == OUTDEV_WINDOW )
277         {
278             pTargetWindow = &static_cast<Window&>(rOutDev); // TODO(Q3): Evil downcast.
279 
280             // we're double-buffered, thus no need for paint area-limiting
281             // clips. besides that, will interfere with animations (as for
282             // Window-invalidate repaints, only parts of the window will
283             // be redrawn otherwise)
284             const Region aFullWindowRegion( Rectangle(aEmptyPoint,
285                                                       aOutDevSize) );
286             pTargetWindow->ExpandPaintClipRegion(aFullWindowRegion);
287         }
288 
289         // TODO(P1): Might be worthwile to track areas of background
290         // changes, too.
291         if( !bUpdateAll && !io_bSurfaceDirty )
292         {
293             if( mbShowFrameInfo )
294             {
295                 // also repaint background below frame counter (fake
296                 // that as a sprite vanishing in this area)
297                 mpRedrawManager->updateSprite( ::canvas::Sprite::Reference(),
298                                                ::basegfx::B2DPoint(),
299                                                ::basegfx::B2DRectangle( 0.0, 0.0,
300                                                                         FPS_BOUNDS.Right(),
301                                                                         FPS_BOUNDS.Bottom() ) );
302             }
303 
304             // background has not changed, so we're free to optimize
305             // repaint to areas where a sprite has changed
306 
307             // process each independent area of overlapping sprites
308             // separately.
309             mpRedrawManager->forEachSpriteArea( *this );
310         }
311         else
312         {
313             // background has changed, so we currently have no choice
314             // but repaint everything (or caller requested that)
315 
316             maVDev->SetOutputSizePixel( aOutDevSize );
317             maVDev->EnableMapMode( sal_False );
318             maVDev->DrawOutDev( aEmptyPoint, aOutDevSize,
319                                 aEmptyPoint, aOutDevSize,
320                                 rBackOutDev );
321 
322             // repaint all active sprites on top of background into
323             // VDev.
324             mpRedrawManager->forEachSprite(
325                 ::boost::bind(
326                     &spriteRedraw,
327                     ::boost::ref( maVDev.get() ),
328                     _1 ) );
329 
330             // flush to screen
331             rOutDev.EnableMapMode( sal_False );
332             rOutDev.SetClipRegion();
333             rOutDev.DrawOutDev( aEmptyPoint, aOutDevSize,
334                                 aEmptyPoint, aOutDevSize,
335                                 *maVDev );
336         }
337 
338         // change record vector must be cleared, for the next turn of
339         // rendering and sprite changing
340         mpRedrawManager->clearChangeRecords();
341 
342         io_bSurfaceDirty = false;
343 
344         if( mbShowFrameInfo )
345         {
346             renderFrameCounter( rOutDev );
347             renderSpriteCount( rOutDev );
348             renderMemUsage( rOutDev );
349         }
350 
351 #if defined(VERBOSE) && OSL_DEBUG_LEVEL > 0
352         static ::canvas::tools::ElapsedTime aElapsedTime;
353 
354         // log time immediately after surface flip
355         OSL_TRACE( "SpriteCanvasHelper::updateScreen(): flip done at %f",
356                    aElapsedTime.getElapsedTime() );
357 #endif
358 
359         // sync output with screen, to ensure that we don't queue up
360         // render requests (calling code might rely on timing,
361         // i.e. assume that things are visible on screen after
362         // updateScreen() returns).
363         if( pTargetWindow )
364         {
365             // commit to screen
366             pTargetWindow->Sync();
367         }
368 
369         return sal_True;
370     }
371 
372     void SpriteCanvasHelper::backgroundPaint( const ::basegfx::B2DRange& rUpdateRect )
373     {
374         ENSURE_OR_THROW( mpOwningSpriteCanvas &&
375                          mpOwningSpriteCanvas->getBackBuffer() &&
376                          mpOwningSpriteCanvas->getFrontBuffer(),
377                          "SpriteCanvasHelper::backgroundPaint(): NULL device pointer " );
378 
379         OutputDevice& 		rOutDev( mpOwningSpriteCanvas->getFrontBuffer()->getOutDev() );
380         BackBufferSharedPtr pBackBuffer( mpOwningSpriteCanvas->getBackBuffer() );
381         OutputDevice& 		rBackOutDev( pBackBuffer->getOutDev() );
382 
383         repaintBackground( rOutDev, rBackOutDev, rUpdateRect );
384     }
385 
386     void SpriteCanvasHelper::scrollUpdate( const ::basegfx::B2DRange& 						rMoveStart,
387                                            const ::basegfx::B2DRange& 						rMoveEnd,
388                                            const ::canvas::SpriteRedrawManager::UpdateArea& rUpdateArea )
389     {
390         ENSURE_OR_THROW( mpOwningSpriteCanvas &&
391                          mpOwningSpriteCanvas->getBackBuffer() &&
392                          mpOwningSpriteCanvas->getFrontBuffer(),
393                          "SpriteCanvasHelper::scrollUpdate(): NULL device pointer " );
394 
395         OutputDevice& 		rOutDev( mpOwningSpriteCanvas->getFrontBuffer()->getOutDev() );
396         BackBufferSharedPtr pBackBuffer( mpOwningSpriteCanvas->getBackBuffer() );
397         OutputDevice& 		rBackOutDev( pBackBuffer->getOutDev() );
398 
399         const Size&                rTargetSizePixel( rOutDev.GetOutputSizePixel() );
400         const ::basegfx::B2IRange  aOutputBounds( 0,0,
401                                                   rTargetSizePixel.Width(),
402                                                   rTargetSizePixel.Height() );
403 
404         // round rectangles to integer pixel. Note: have to be
405         // extremely careful here, to avoid off-by-one errors for
406         // the destination area: otherwise, the next scroll update
407         // would copy pixel that are not supposed to be part of
408         // the sprite.
409         ::basegfx::B2IRange aSourceRect(
410             ::canvas::tools::spritePixelAreaFromB2DRange( rMoveStart ) );
411         const ::basegfx::B2IRange& rDestRect(
412             ::canvas::tools::spritePixelAreaFromB2DRange( rMoveEnd ) );
413         ::basegfx::B2IPoint aDestPos( rDestRect.getMinimum() );
414 
415         ::std::vector< ::basegfx::B2IRange > aUnscrollableAreas;
416 
417         // Since strictly speaking, this scroll algorithm is plain
418         // buggy, the scrolled area might actually lie _below_ another
419         // window - we've made this feature configurable via
420         // mbIsUnsafeScrolling.
421 
422         // clip to output bounds (cannot properly scroll stuff
423         // _outside_ our screen area)
424         if( !mbIsUnsafeScrolling ||
425             !::canvas::tools::clipScrollArea( aSourceRect,
426                                               aDestPos,
427                                               aUnscrollableAreas,
428                                               aOutputBounds ) )
429         {
430             // fully clipped scroll area: cannot simply scroll
431             // then. Perform normal opaque update (can use that, since
432             // one of the preconditions for scrollable update is
433             // opaque sprite content)
434 
435             // repaint all affected sprites directly to output device
436             ::std::for_each( rUpdateArea.maComponentList.begin(),
437                              rUpdateArea.maComponentList.end(),
438                              ::boost::bind(
439                                  &spriteRedrawStub3,
440                                  ::boost::ref( rOutDev ),
441                                  _1 ) );
442         }
443         else
444         {
445             // scroll rOutDev content
446             rOutDev.CopyArea( ::vcl::unotools::pointFromB2IPoint( aDestPos ),
447                               ::vcl::unotools::pointFromB2IPoint( aSourceRect.getMinimum() ),
448                               // TODO(Q2): use numeric_cast to check range
449                               ::Size( static_cast<sal_Int32>(aSourceRect.getRange().getX()),
450                                       static_cast<sal_Int32>(aSourceRect.getRange().getY()) ) );
451 
452             const ::canvas::SpriteRedrawManager::SpriteConnectedRanges::ComponentListType::const_iterator
453                 aFirst( rUpdateArea.maComponentList.begin() );
454             ::canvas::SpriteRedrawManager::SpriteConnectedRanges::ComponentListType::const_iterator
455                   aSecond( aFirst ); ++aSecond;
456 
457             ENSURE_OR_THROW( aFirst->second.getSprite().is(),
458                               "VCLCanvas::scrollUpdate(): no sprite" );
459 
460             // repaint uncovered areas from sprite. Need to actually
461             // clip here, since we're only repainting _parts_ of the
462             // sprite
463             rOutDev.Push( PUSH_CLIPREGION );
464             ::std::for_each( aUnscrollableAreas.begin(),
465                              aUnscrollableAreas.end(),
466                              ::boost::bind( &opaqueUpdateSpriteArea,
467                                             ::boost::cref(aFirst->second.getSprite()),
468                                             ::boost::ref(rOutDev),
469                                             _1 ) );
470             rOutDev.Pop();
471         }
472 
473         // repaint uncovered areas from backbuffer - take the
474         // _rounded_ rectangles from above, to have the update
475         // consistent with the scroll above.
476         ::std::vector< ::basegfx::B2DRange > aUncoveredAreas;
477         ::basegfx::computeSetDifference( aUncoveredAreas,
478                                          rUpdateArea.maTotalBounds,
479                                          ::basegfx::B2DRange( rDestRect ) );
480         ::std::for_each( aUncoveredAreas.begin(),
481                          aUncoveredAreas.end(),
482                          ::boost::bind( &repaintBackground,
483                                         ::boost::ref(rOutDev),
484                                         ::boost::ref(rBackOutDev),
485                                         _1 ) );
486     }
487 
488     void SpriteCanvasHelper::opaqueUpdate( const ::basegfx::B2DRange&                          rTotalArea,
489                                            const ::std::vector< ::canvas::Sprite::Reference >& rSortedUpdateSprites )
490     {
491         (void)rTotalArea;
492 
493         ENSURE_OR_THROW( mpOwningSpriteCanvas &&
494                          mpOwningSpriteCanvas->getBackBuffer() &&
495                          mpOwningSpriteCanvas->getFrontBuffer(),
496                          "SpriteCanvasHelper::opaqueUpdate(): NULL device pointer " );
497 
498         OutputDevice& rOutDev( mpOwningSpriteCanvas->getFrontBuffer()->getOutDev() );
499 
500         // no need to clip output to actual update region - there will
501         // always be ALL sprites contained in the rectangular update
502         // area containd in rTotalArea (that's the way
503         // B2DConnectedRanges work). If rTotalArea appears to be
504         // smaller than the sprite - then this sprite carries a clip,
505         // and the update will be constrained to that rect.
506 
507         // repaint all affected sprites directly to output device
508         ::std::for_each( rSortedUpdateSprites.begin(),
509                          rSortedUpdateSprites.end(),
510                          ::boost::bind(
511                              &spriteRedrawStub,
512                              ::boost::ref( rOutDev ),
513                              _1 ) );
514     }
515 
516     void SpriteCanvasHelper::genericUpdate( const ::basegfx::B2DRange&                          rRequestedArea,
517                                             const ::std::vector< ::canvas::Sprite::Reference >& rSortedUpdateSprites )
518     {
519         ENSURE_OR_THROW( mpOwningSpriteCanvas &&
520                          mpOwningSpriteCanvas->getBackBuffer() &&
521                          mpOwningSpriteCanvas->getFrontBuffer(),
522                          "SpriteCanvasHelper::genericUpdate(): NULL device pointer " );
523 
524         OutputDevice& 		rOutDev( mpOwningSpriteCanvas->getFrontBuffer()->getOutDev() );
525         BackBufferSharedPtr pBackBuffer( mpOwningSpriteCanvas->getBackBuffer() );
526         OutputDevice& 		rBackOutDev( pBackBuffer->getOutDev() );
527 
528         // limit size of update VDev to target outdev's size
529         const Size& rTargetSizePixel( rOutDev.GetOutputSizePixel() );
530 
531         // round output position towards zero. Don't want to truncate
532         // a fraction of a sprite pixel...  Clip position at origin,
533         // otherwise, truncation of size below might leave visible
534         // areas uncovered by VDev.
535         const ::Point aOutputPosition(
536             ::std::max( sal_Int32( 0 ),
537                         static_cast< sal_Int32 >(rRequestedArea.getMinX()) ),
538             ::std::max( sal_Int32( 0 ),
539                         static_cast< sal_Int32 >(rRequestedArea.getMinY()) ) );
540         // round output size towards +infty. Don't want to truncate a
541         // fraction of a sprite pixel... Limit coverage of VDev to
542         // output device's area (i.e. not only to total size, but to
543         // cover _only_ the visible parts).
544         const ::Size aOutputSize(
545             ::std::max( sal_Int32( 0 ),
546                         ::std::min( static_cast< sal_Int32 >(rTargetSizePixel.Width() - aOutputPosition.X()),
547                                     ::canvas::tools::roundUp( rRequestedArea.getMaxX() - aOutputPosition.X() ))),
548             ::std::max( sal_Int32( 0 ),
549                         ::std::min( static_cast< sal_Int32 >(rTargetSizePixel.Height() - aOutputPosition.Y()),
550                                     ::canvas::tools::roundUp( rRequestedArea.getMaxY() - aOutputPosition.Y() ))));
551 
552         // early exit for empty output area.
553         if( aOutputSize.Width() == 0 &&
554             aOutputSize.Height() == 0 )
555         {
556             return;
557         }
558 
559         const Point aEmptyPoint(0,0);
560         const Size  aCurrOutputSize( maVDev->GetOutputSizePixel() );
561 
562         // adapt maVDev's size to the area that actually needs the
563         // repaint.
564         if( aCurrOutputSize.Width() < aOutputSize.Width() ||
565             aCurrOutputSize.Height() < aOutputSize.Height() )
566         {
567             // TODO(P1): Come up with a clever tactic to reduce maVDev
568             // from time to time. Reduction with threshold (say, if
569             // maVDev is more than twice too large) is not wise, as
570             // this might then toggle within the same updateScreen(),
571             // but for different disjunct sprite areas.
572             maVDev->SetOutputSizePixel( aOutputSize );
573         }
574 
575         // paint background
576         maVDev->EnableMapMode( sal_False );
577         maVDev->SetClipRegion();
578         maVDev->DrawOutDev( aEmptyPoint, aOutputSize,
579                             aOutputPosition, aOutputSize,
580                             rBackOutDev );
581 
582         // repaint all affected sprites on top of background into
583         // VDev.
584         ::std::for_each( rSortedUpdateSprites.begin(),
585                          rSortedUpdateSprites.end(),
586                          ::boost::bind( &spriteRedrawStub2,
587                                         ::boost::ref( maVDev.get() ),
588                                         ::boost::cref(
589                                             ::vcl::unotools::b2DPointFromPoint(aOutputPosition)),
590                                         _1 ) );
591 
592         // flush to screen
593         rOutDev.EnableMapMode( sal_False );
594         rOutDev.DrawOutDev( aOutputPosition, aOutputSize,
595                             aEmptyPoint, aOutputSize,
596                             *maVDev );
597     }
598 
599     void SpriteCanvasHelper::renderFrameCounter( OutputDevice& rOutDev )
600     {
601         const double denominator( maLastUpdate.getElapsedTime() );
602         maLastUpdate.reset();
603 
604         ::rtl::OUString text( ::rtl::math::doubleToUString( denominator == 0.0 ? 100.0 : 1.0/denominator,
605                                                             rtl_math_StringFormat_F,
606                                                             2,'.',NULL,' ') );
607 
608         // pad with leading space
609         while( text.getLength() < 6 )
610             text = ::rtl::OUString(RTL_CONSTASCII_USTRINGPARAM (" ")) + text;
611 
612         text += ::rtl::OUString(RTL_CONSTASCII_USTRINGPARAM (" fps"));
613 
614         renderInfoText( rOutDev,
615                         text,
616                         Point(0, 0) );
617     }
618 
619     namespace
620     {
621         template< typename T > struct Adder
622         {
623             typedef void result_type;
624 
625             Adder( T& rAdderTarget,
626                    T  nIncrement ) :
627                 mpTarget( &rAdderTarget ),
628                 mnIncrement( nIncrement )
629             {
630             }
631 
632             void operator()() { *mpTarget += mnIncrement; }
633             void operator()( const ::canvas::Sprite::Reference& ) { *mpTarget += mnIncrement; }
634             void operator()( T nIncrement ) { *mpTarget += nIncrement; }
635 
636             T* mpTarget;
637             T  mnIncrement;
638         };
639 
640         template< typename T> Adder<T> makeAdder( T& rAdderTarget,
641                                                   T  nIncrement )
642         {
643             return Adder<T>(rAdderTarget, nIncrement);
644         }
645     }
646 
647     void SpriteCanvasHelper::renderSpriteCount( OutputDevice& rOutDev )
648     {
649         if( mpRedrawManager )
650         {
651             sal_Int32 nCount(0);
652 
653             mpRedrawManager->forEachSprite( makeAdder(nCount,sal_Int32(1)) );
654             ::rtl::OUString text(
655                 ::rtl::OUString::valueOf(
656                     // disambiguate overload...
657                     static_cast<sal_Int64>(nCount) ) );
658 
659             // pad with leading space
660             while( text.getLength() < 3 )
661                 text = ::rtl::OUString(RTL_CONSTASCII_USTRINGPARAM (" ")) + text;
662 
663             text = ::rtl::OUString(RTL_CONSTASCII_USTRINGPARAM ("Sprites: ")) + text;
664 
665             renderInfoText( rOutDev,
666                             text,
667                             Point(0, 30) );
668         }
669     }
670 
671     void SpriteCanvasHelper::renderMemUsage( OutputDevice& rOutDev )
672     {
673         BackBufferSharedPtr pBackBuffer( mpOwningSpriteCanvas->getBackBuffer() );
674 
675         if( mpRedrawManager &&
676             pBackBuffer )
677         {
678             double nPixel(0.0);
679 
680             // accumulate pixel count for each sprite into fCount
681             mpRedrawManager->forEachSprite( ::boost::bind(
682                                                 makeAdder(nPixel,1.0),
683                                                 ::boost::bind(
684                                                     &calcNumPixel,
685                                                     _1 ) ) );
686 
687             static const int NUM_VIRDEV(2);
688             static const int BYTES_PER_PIXEL(3);
689 
690             const Size& rVDevSize( maVDev->GetOutputSizePixel() );
691             const Size& rBackBufferSize( pBackBuffer->getOutDev().GetOutputSizePixel() );
692 
693             const double nMemUsage( nPixel * NUM_VIRDEV * BYTES_PER_PIXEL +
694                                     rVDevSize.Width()*rVDevSize.Height() * BYTES_PER_PIXEL +
695                                     rBackBufferSize.Width()*rBackBufferSize.Height() * BYTES_PER_PIXEL );
696 
697             ::rtl::OUString text( ::rtl::math::doubleToUString( nMemUsage / 1048576.0,
698                                                                 rtl_math_StringFormat_F,
699                                                                 2,'.',NULL,' ') );
700 
701             // pad with leading space
702             while( text.getLength() < 4 )
703                 text = ::rtl::OUString(RTL_CONSTASCII_USTRINGPARAM (" ")) + text;
704 
705             text = ::rtl::OUString(RTL_CONSTASCII_USTRINGPARAM ("Mem: ")) +
706                 text +
707                 ::rtl::OUString(RTL_CONSTASCII_USTRINGPARAM ("MB"));
708 
709             renderInfoText( rOutDev,
710                             text,
711                             Point(0, 60) );
712         }
713     }
714 }
715