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