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