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/spriteredrawmanager.hxx>
30 
31 #include <basegfx/range/b2drectangle.hxx>
32 #include <basegfx/tools/canvastools.hxx>
33 #include <basegfx/vector/b2dsize.hxx>
34 #include <basegfx/range/rangeexpander.hxx>
35 
36 #include <algorithm>
37 #include <functional>
38 #include <boost/bind.hpp>
39 
40 
41 namespace canvas
42 {
43     namespace
44     {
45         /** Helper class to condense sprite updates into a single action
46 
47         	This class tracks the sprite changes over the recorded
48         	change list, and generates a single update action from
49         	that (note that per screen update, several moves,
50         	visibility changes and content updates might happen)
51          */
52         class SpriteTracer
53         {
54         public:
SpriteTracer(const Sprite::Reference & rAffectedSprite)55             SpriteTracer( const Sprite::Reference& rAffectedSprite ) :
56                 mpAffectedSprite(rAffectedSprite),
57                 maMoveStartArea(),
58                 maMoveEndArea(),
59                 mbIsMove( false ),
60                 mbIsGenericUpdate( false )
61             {
62             }
63 
operator ()(const SpriteRedrawManager::SpriteChangeRecord & rSpriteRecord)64             void operator()( const SpriteRedrawManager::SpriteChangeRecord& rSpriteRecord )
65             {
66                 // only deal with change events from the currently
67                 // affected sprite
68                 if( rSpriteRecord.mpAffectedSprite == mpAffectedSprite )
69                 {
70                     switch( rSpriteRecord.meChangeType )
71                     {
72                         case SpriteRedrawManager::SpriteChangeRecord::move:
73                             if( !mbIsMove )
74                             {
75                                 // no move yet - this must be the first one
76                                 maMoveStartArea = ::basegfx::B2DRectangle(
77                                     rSpriteRecord.maOldPos,
78                                     rSpriteRecord.maOldPos + rSpriteRecord.maUpdateArea.getRange() );
79                                 mbIsMove	 	= true;
80                             }
81 
82                             maMoveEndArea	= rSpriteRecord.maUpdateArea;
83                             break;
84 
85                         case SpriteRedrawManager::SpriteChangeRecord::update:
86                             // update end update area of the
87                             // sprite. Thus, every update() action
88                             // _after_ the last move will correctly
89                             // update the final repaint area. And this
90                             // does not interfere with subsequent
91                             // moves, because moves always perform a
92                             // hard set of maMoveEndArea to their
93                             // stored value
94                             maMoveEndArea.expand( rSpriteRecord.maUpdateArea );
95                             mbIsGenericUpdate = true;
96                             break;
97 
98                         default:
99                             ENSURE_OR_THROW( false,
100                                               "Unexpected case in SpriteUpdater::operator()" );
101                             break;
102                     }
103                 }
104             }
105 
commit(SpriteRedrawManager::SpriteConnectedRanges & rUpdateCollector) const106             void commit( SpriteRedrawManager::SpriteConnectedRanges& rUpdateCollector ) const
107             {
108                 if( mbIsMove )
109                 {
110                     if( !maMoveStartArea.isEmpty() ||
111                         !maMoveEndArea.isEmpty() )
112                     {
113                         // if mbIsGenericUpdate is false, this is a
114                         // pure move (i.e. no other update
115                         // operations). Pass that information on to
116                         // the SpriteInfo
117                         const bool bIsPureMove( !mbIsGenericUpdate );
118 
119                         // ignore the case that start and end update
120                         // area overlap - the b2dconnectedranges
121                         // handle that, anyway. doing it this way
122                         // ensures that we have both old and new area
123                         // stored
124 
125                         // round all given range up to enclosing
126                         // integer rectangle - since the whole thing
127                         // here is about
128 
129                         // first, draw the new sprite position
130                         rUpdateCollector.addRange(
131                             ::basegfx::unotools::b2DSurroundingIntegerRangeFromB2DRange( maMoveEndArea ),
132                             SpriteRedrawManager::SpriteInfo(
133                                 mpAffectedSprite,
134                                 maMoveEndArea,
135                                 true,
136                                 bIsPureMove ) );
137 
138                         // then, clear the old place (looks smoother
139                         // this way)
140                         rUpdateCollector.addRange(
141                             ::basegfx::unotools::b2DSurroundingIntegerRangeFromB2DRange( maMoveStartArea ),
142                             SpriteRedrawManager::SpriteInfo(
143                                 Sprite::Reference(),
144                                 maMoveStartArea,
145                                 true,
146                                 bIsPureMove ) );
147                     }
148                 }
149                 else if( mbIsGenericUpdate &&
150                          !maMoveEndArea.isEmpty() )
151                 {
152                     rUpdateCollector.addRange(
153                         ::basegfx::unotools::b2DSurroundingIntegerRangeFromB2DRange( maMoveEndArea ),
154                         SpriteRedrawManager::SpriteInfo(
155                             mpAffectedSprite,
156                             maMoveEndArea,
157                             true ) );
158                 }
159             }
160 
161         private:
162             Sprite::Reference		mpAffectedSprite;
163             ::basegfx::B2DRectangle	maMoveStartArea;
164             ::basegfx::B2DRectangle	maMoveEndArea;
165 
166             /// True, if at least one move was encountered
167             bool					mbIsMove;
168 
169             /// True, if at least one generic update was encountered
170             bool					mbIsGenericUpdate;
171         };
172 
173 
174         /** SpriteChecker functor, which for every sprite checks the
175             given update vector for necessary screen updates
176          */
177         class SpriteUpdater
178         {
179         public:
180             /** Generate update area list
181 
182             	@param rUpdater
183                 Reference to an updater object, which will receive the
184                 update areas.
185 
186                 @param rChangeContainer
187                 Container with all sprite change requests
188 
189              */
SpriteUpdater(SpriteRedrawManager::SpriteConnectedRanges & rUpdater,const SpriteRedrawManager::VectorOfChangeRecords & rChangeContainer)190             SpriteUpdater( SpriteRedrawManager::SpriteConnectedRanges&			rUpdater,
191                            const SpriteRedrawManager::VectorOfChangeRecords&	rChangeContainer ) :
192                 mrUpdater( rUpdater ),
193                 mrChangeContainer( rChangeContainer )
194             {
195             }
196 
197             /** Call this method for every sprite on your screen
198 
199             	This method scans the change container, collecting all
200             	update info for the given sprite into one or two
201             	update operations, which in turn are inserted into the
202             	connected ranges processor.
203 
204                 @param rSprite
205                 Current sprite to collect update info for.
206              */
operator ()(const Sprite::Reference & rSprite)207             void operator()( const Sprite::Reference& rSprite )
208             {
209                 const SpriteTracer aSpriteTracer(
210                     ::std::for_each( mrChangeContainer.begin(),
211                                      mrChangeContainer.end(),
212                                      SpriteTracer( rSprite ) ) );
213 
214                 aSpriteTracer.commit( mrUpdater );
215             }
216 
217         private:
218             SpriteRedrawManager::SpriteConnectedRanges&			mrUpdater;
219             const SpriteRedrawManager::VectorOfChangeRecords&	mrChangeContainer;
220         };
221     }
222 
setupUpdateAreas(SpriteConnectedRanges & rUpdateAreas) const223     void SpriteRedrawManager::setupUpdateAreas( SpriteConnectedRanges& rUpdateAreas ) const
224     {
225         // TODO(T3): This is NOT thread safe at all. This only works
226         // under the assumption that NOBODY changes ANYTHING
227         // concurrently, while this method is on the stack. We should
228         // really rework the canvas::Sprite interface, in such a way
229         // that it dumps ALL its state with a single, atomic
230         // call. Then, we store that state locally. This prolly goes
231         // in line with the problem of having sprite state available
232         // for the frame before the last frame; plus, it avoids
233         // frequent locks of the object mutices
234         SpriteComparator aSpriteComparator;
235 
236         // put all sprites that have changed content into update areas
237         ListOfSprites::const_iterator       aCurrSprite( maSprites.begin() );
238         const ListOfSprites::const_iterator aEndSprite ( maSprites.end() );
239         while( aCurrSprite != aEndSprite )
240         {
241             if( (*aCurrSprite)->isContentChanged() )
242                 const_cast<SpriteRedrawManager*>(this)->updateSprite( *aCurrSprite,
243                                                                       (*aCurrSprite)->getPosPixel(),
244                                                                       (*aCurrSprite)->getUpdateArea() );
245             ++aCurrSprite;
246         }
247 
248         // sort sprites after prio
249         VectorOfSprites aSortedSpriteVector;
250         ::std::copy( maSprites.begin(),
251                      maSprites.end(),
252                      ::std::back_insert_iterator< VectorOfSprites >(aSortedSpriteVector) );
253         ::std::sort( aSortedSpriteVector.begin(),
254                      aSortedSpriteVector.end(),
255                      aSpriteComparator );
256 
257         // extract all referenced sprites from the maChangeRecords
258         // (copy sprites, make the list unique, regarding the
259         // sprite pointer). This assumes that, until this scope
260         // ends, nobody changes the maChangeRecords vector!
261         VectorOfSprites aUpdatableSprites;
262         VectorOfChangeRecords::const_iterator       aCurrRecord( maChangeRecords.begin() );
263         const VectorOfChangeRecords::const_iterator aEndRecords( maChangeRecords.end() );
264         while( aCurrRecord != aEndRecords )
265         {
266             const Sprite::Reference& rSprite( aCurrRecord->getSprite() );
267             if( rSprite.is() )
268                 aUpdatableSprites.push_back( rSprite );
269             ++aCurrRecord;
270         }
271 
272         VectorOfSprites::iterator aBegin( aUpdatableSprites.begin() );
273         VectorOfSprites::iterator aEnd  ( aUpdatableSprites.end() );
274         ::std::sort( aBegin,
275                      aEnd,
276                      aSpriteComparator );
277 
278         aEnd = ::std::unique( aBegin, aEnd );
279 
280         // for each unique sprite, check the change event vector,
281         // calculate the update operation from that, and add the
282         // result to the aUpdateArea.
283         ::std::for_each( aBegin,
284                          aEnd,
285                          SpriteUpdater( rUpdateAreas,
286                                         maChangeRecords) );
287 
288         // TODO(P2): Implement your own output iterator adapter, to
289         // avoid that totally superfluous temp aUnchangedSprites
290         // vector.
291 
292         // add all sprites to rUpdateAreas, that are _not_ already
293         // contained in the uniquified vector of changed ones
294         // (i.e. the difference between aSortedSpriteVector and
295         // aUpdatableSprites).
296         VectorOfSprites aUnchangedSprites;
297         ::std::set_difference( aSortedSpriteVector.begin(),
298                                aSortedSpriteVector.end(),
299                                aBegin, aEnd,
300                                ::std::back_insert_iterator< VectorOfSprites >(aUnchangedSprites) );
301 
302         // add each remaining unchanged sprite to connected ranges,
303         // marked as "don't need update"
304         VectorOfSprites::const_iterator 		aCurr( aUnchangedSprites.begin() );
305         const VectorOfSprites::const_iterator	aEnd2( aUnchangedSprites.end() );
306         while( aCurr != aEnd2 )
307         {
308             const ::basegfx::B2DRange& rUpdateArea( (*aCurr)->getUpdateArea() );
309             rUpdateAreas.addRange(
310                 ::basegfx::unotools::b2DSurroundingIntegerRangeFromB2DRange( rUpdateArea ),
311                 SpriteInfo(*aCurr,
312                            rUpdateArea,
313                            false) );
314             ++aCurr;
315         }
316     }
317 
318 #if OSL_DEBUG_LEVEL > 0
impIsEqualB2DRange(const basegfx::B2DRange & rRangeA,const basegfx::B2DRange & rRangeB,double fSmallValue)319     bool impIsEqualB2DRange(const basegfx::B2DRange& rRangeA, const basegfx::B2DRange& rRangeB, double fSmallValue)
320     {
321         return fabs(rRangeB.getMinX() - rRangeA.getMinX()) <= fSmallValue
322             && fabs(rRangeB.getMinY() - rRangeA.getMinY()) <= fSmallValue
323             && fabs(rRangeB.getMaxX() - rRangeA.getMaxX()) <= fSmallValue
324             && fabs(rRangeB.getMaxY() - rRangeA.getMaxY()) <= fSmallValue;
325     }
326 
impIsEqualB2DVector(const basegfx::B2DVector & rVecA,const basegfx::B2DVector & rVecB,double fSmallValue)327     bool impIsEqualB2DVector(const basegfx::B2DVector& rVecA, const basegfx::B2DVector& rVecB, double fSmallValue)
328     {
329         return fabs(rVecB.getX() - rVecA.getX()) <= fSmallValue
330             && fabs(rVecB.getY() - rVecA.getY()) <= fSmallValue;
331     }
332 #endif
333 
isAreaUpdateScroll(::basegfx::B2DRectangle & o_rMoveStart,::basegfx::B2DRectangle & o_rMoveEnd,const UpdateArea & rUpdateArea,::std::size_t nNumSprites) const334     bool SpriteRedrawManager::isAreaUpdateScroll( ::basegfx::B2DRectangle& 	o_rMoveStart,
335                                                   ::basegfx::B2DRectangle& 	o_rMoveEnd,
336                                                   const UpdateArea& 		rUpdateArea,
337                                                   ::std::size_t				nNumSprites ) const
338     {
339         // check for a solitary move, which consists of exactly two
340         // pure-move entries, the first with valid, the second with
341         // invalid sprite (see SpriteTracer::commit()).  Note that we
342         // cannot simply store some flag in SpriteTracer::commit()
343         // above and just check that here, since during the connected
344         // range calculations, other sprites might get merged into the
345         // same region (thus spoiling the scrolling move
346         // optimization).
347         if( nNumSprites != 2 )
348             return false;
349 
350         const SpriteConnectedRanges::ComponentListType::const_iterator aFirst(
351             rUpdateArea.maComponentList.begin() );
352         SpriteConnectedRanges::ComponentListType::const_iterator aSecond(
353             aFirst ); ++aSecond;
354 
355         if( !aFirst->second.isPureMove() ||
356             !aSecond->second.isPureMove() ||
357             !aFirst->second.getSprite().is() ||
358             // use _true_ update area, not the rounded version
359             !aFirst->second.getSprite()->isAreaUpdateOpaque( aFirst->second.getUpdateArea() ) ||
360             aSecond->second.getSprite().is() )
361         {
362             // either no move update, or incorrect sprite, or sprite
363             // content not fully opaque over update region.
364             return false;
365         }
366 
367         o_rMoveStart 	  = aSecond->second.getUpdateArea();
368         o_rMoveEnd   	  = aFirst->second.getUpdateArea();
369 
370 #if OSL_DEBUG_LEVEL > 0
371         ::basegfx::B2DRectangle aTotalBounds( o_rMoveStart );
372         aTotalBounds.expand( o_rMoveEnd );
373 
374         OSL_POSTCOND(impIsEqualB2DRange(rUpdateArea.maTotalBounds, basegfx::unotools::b2DSurroundingIntegerRangeFromB2DRange(aTotalBounds), 0.5),
375             "SpriteRedrawManager::isAreaUpdateScroll(): sprite area and total area mismatch");
376         OSL_POSTCOND(impIsEqualB2DVector(o_rMoveStart.getRange(), o_rMoveEnd.getRange(), 0.5),
377             "SpriteRedrawManager::isAreaUpdateScroll(): scroll start and end area have mismatching size");
378 #endif
379 
380         return true;
381     }
382 
isAreaUpdateNotOpaque(const::basegfx::B2DRectangle & rUpdateRect,const AreaComponent & rComponent) const383     bool SpriteRedrawManager::isAreaUpdateNotOpaque( const ::basegfx::B2DRectangle& rUpdateRect,
384                                                      const AreaComponent&			rComponent ) const
385     {
386         const Sprite::Reference& pAffectedSprite( rComponent.second.getSprite() );
387 
388         if( !pAffectedSprite.is() )
389             return true; // no sprite, no opaque update!
390 
391         return !pAffectedSprite->isAreaUpdateOpaque( rUpdateRect );
392     }
393 
isAreaUpdateOpaque(const UpdateArea & rUpdateArea,::std::size_t nNumSprites) const394     bool SpriteRedrawManager::isAreaUpdateOpaque( const UpdateArea&	rUpdateArea,
395                                                   ::std::size_t		nNumSprites ) const
396     {
397         // check whether the sprites in the update area's list will
398         // fully cover the given area _and_ do that in an opaque way
399         // (i.e. no alpha, no non-rectangular sprite content).
400 
401         // TODO(P1): Come up with a smarter early-exit criterion here
402         // (though, I think, the case that _lots_ of sprites _fully_
403         // cover a rectangular area _without_ any holes is extremely
404         // improbable)
405 
406         // avoid checking large number of sprites (and probably fail,
407         // anyway). Note: the case nNumSprites < 1 should normally not
408         // happen, as handleArea() calls backgroundPaint() then.
409         if( nNumSprites > 3 || nNumSprites < 1 )
410             return false;
411 
412         const SpriteConnectedRanges::ComponentListType::const_iterator aBegin(
413             rUpdateArea.maComponentList.begin() );
414         const SpriteConnectedRanges::ComponentListType::const_iterator aEnd(
415             rUpdateArea.maComponentList.end() );
416 
417         // now, calc the _true_ update area, by merging all sprite's
418         // true update areas into one rectangle
419         ::basegfx::B2DRange aTrueArea( aBegin->second.getUpdateArea() );
420         ::std::for_each( aBegin,
421                          aEnd,
422                          ::boost::bind( ::basegfx::B2DRangeExpander(aTrueArea),
423                                         ::boost::bind( &SpriteInfo::getUpdateArea,
424                                                        ::boost::bind( ::std::select2nd<AreaComponent>(),
425                                                                       _1 ) ) ) );
426 
427         // and check whether _any_ of the sprites tells that its area
428         // update will not be opaque.
429         return (::std::find_if( aBegin,
430                                 aEnd,
431                                 ::boost::bind( &SpriteRedrawManager::isAreaUpdateNotOpaque,
432                                                this,
433                                                ::boost::cref(aTrueArea),
434                                                _1 ) ) == aEnd );
435     }
436 
areSpritesChanged(const UpdateArea & rUpdateArea) const437     bool SpriteRedrawManager::areSpritesChanged( const UpdateArea& rUpdateArea ) const
438     {
439         // check whether SpriteInfo::needsUpdate returns false for
440         // all elements of this area's contained sprites
441         //
442         // if not a single changed sprite found - just ignore this
443         // component (return false)
444         const SpriteConnectedRanges::ComponentListType::const_iterator aEnd(
445             rUpdateArea.maComponentList.end() );
446         return (::std::find_if( rUpdateArea.maComponentList.begin(),
447                                 aEnd,
448                                 ::boost::bind( &SpriteInfo::needsUpdate,
449                                                ::boost::bind(
450                                                    ::std::select2nd<SpriteConnectedRanges::ComponentType>(),
451                                                    _1 ) ) ) != aEnd );
452     }
453 
SpriteRedrawManager()454     SpriteRedrawManager::SpriteRedrawManager() :
455         maSprites(),
456         maChangeRecords()
457     {
458     }
459 
disposing()460     void SpriteRedrawManager::disposing()
461     {
462         // drop all references
463         maChangeRecords.clear();
464 
465         // dispose all sprites - the spritecanvas, and by delegation,
466         // this object, is the owner of the sprites. After all, a
467         // sprite without a canvas to render into makes not terribly
468         // much sense.
469 
470         // TODO(Q3): Once boost 1.33 is in, change back to for_each
471         // with ::boost::mem_fn. For the time being, explicit loop due
472         // to cdecl declaration of all UNO methods.
473         ListOfSprites::reverse_iterator aCurr( maSprites.rbegin() );
474         ListOfSprites::reverse_iterator aEnd( maSprites.rend() );
475         while( aCurr != aEnd )
476             (*aCurr++)->dispose();
477 
478         maSprites.clear();
479     }
480 
clearChangeRecords()481     void SpriteRedrawManager::clearChangeRecords()
482     {
483         maChangeRecords.clear();
484     }
485 
showSprite(const Sprite::Reference & rSprite)486     void SpriteRedrawManager::showSprite( const Sprite::Reference& rSprite )
487     {
488         maSprites.push_back( rSprite );
489     }
490 
hideSprite(const Sprite::Reference & rSprite)491     void SpriteRedrawManager::hideSprite( const Sprite::Reference& rSprite )
492     {
493         maSprites.remove( rSprite );
494     }
495 
moveSprite(const Sprite::Reference & rSprite,const::basegfx::B2DPoint & rOldPos,const::basegfx::B2DPoint & rNewPos,const::basegfx::B2DVector & rSpriteSize)496     void SpriteRedrawManager::moveSprite( const Sprite::Reference&		rSprite,
497                                           const ::basegfx::B2DPoint& 	rOldPos,
498                                           const ::basegfx::B2DPoint&	rNewPos,
499                                           const ::basegfx::B2DVector& 	rSpriteSize )
500     {
501         maChangeRecords.push_back( SpriteChangeRecord( rSprite,
502                                                        rOldPos,
503                                                        rNewPos,
504                                                        rSpriteSize ) );
505     }
506 
updateSprite(const Sprite::Reference & rSprite,const::basegfx::B2DPoint & rPos,const::basegfx::B2DRange & rUpdateArea)507     void SpriteRedrawManager::updateSprite( const Sprite::Reference& 	rSprite,
508                                             const ::basegfx::B2DPoint& 	rPos,
509                                             const ::basegfx::B2DRange&	rUpdateArea )
510     {
511         maChangeRecords.push_back( SpriteChangeRecord( rSprite,
512                                                        rPos,
513                                                        rUpdateArea ) );
514     }
515 
516 }
517