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 31 #include <rtl/math.hxx> 32 33 #include <vcl/outdev.hxx> 34 #include <vcl/bitmap.hxx> 35 #include <vcl/alpha.hxx> 36 #include <vcl/bitmapex.hxx> 37 #include <vcl/canvastools.hxx> 38 39 #include <basegfx/matrix/b2dhommatrix.hxx> 40 #include <basegfx/point/b2dpoint.hxx> 41 #include <basegfx/tools/canvastools.hxx> 42 #include <basegfx/polygon/b2dpolygon.hxx> 43 #include <basegfx/polygon/b2dpolygontools.hxx> 44 #include <basegfx/polygon/b2dpolypolygontools.hxx> 45 #include <basegfx/polygon/b2dpolygoncutandtouch.hxx> 46 #include <basegfx/polygon/b2dpolygontriangulator.hxx> 47 #include <basegfx/polygon/b2dpolygonclipper.hxx> 48 #include <basegfx/numeric/ftools.hxx> 49 50 #include <canvas/canvastools.hxx> 51 52 #include "spritehelper.hxx" 53 54 using namespace ::com::sun::star; 55 56 57 namespace vclcanvas 58 { SpriteHelper()59 SpriteHelper::SpriteHelper() : 60 mpBackBuffer(), 61 mpBackBufferMask(), 62 maContent(), 63 mbShowSpriteBounds(false) 64 { 65 } 66 init(const geometry::RealSize2D & rSpriteSize,const::canvas::SpriteSurface::Reference & rOwningSpriteCanvas,const BackBufferSharedPtr & rBackBuffer,const BackBufferSharedPtr & rBackBufferMask,bool bShowSpriteBounds)67 void SpriteHelper::init( const geometry::RealSize2D& rSpriteSize, 68 const ::canvas::SpriteSurface::Reference& rOwningSpriteCanvas, 69 const BackBufferSharedPtr& rBackBuffer, 70 const BackBufferSharedPtr& rBackBufferMask, 71 bool bShowSpriteBounds ) 72 { 73 ENSURE_OR_THROW( rOwningSpriteCanvas.get() && rBackBuffer && rBackBufferMask, 74 "SpriteHelper::init(): Invalid sprite canvas or back buffer" ); 75 76 mpBackBuffer = rBackBuffer; 77 mpBackBufferMask = rBackBufferMask; 78 mbShowSpriteBounds = bShowSpriteBounds; 79 80 init( rSpriteSize, rOwningSpriteCanvas ); 81 } 82 disposing()83 void SpriteHelper::disposing() 84 { 85 mpBackBuffer.reset(); 86 mpBackBufferMask.reset(); 87 88 // forward to parent 89 CanvasCustomSpriteHelper::disposing(); 90 } 91 redraw(OutputDevice & rTargetSurface,const::basegfx::B2DPoint & rPos,bool & io_bSurfacesDirty,bool bBufferedUpdate) const92 void SpriteHelper::redraw( OutputDevice& rTargetSurface, 93 const ::basegfx::B2DPoint& rPos, 94 bool& io_bSurfacesDirty, 95 bool bBufferedUpdate ) const 96 { 97 (void)bBufferedUpdate; // not used on every platform 98 99 if( !mpBackBuffer || 100 !mpBackBufferMask ) 101 { 102 return; // we're disposed 103 } 104 105 // log output pos in device pixel 106 VERBOSE_TRACE( "SpriteHelper::redraw(): output pos is (%f, %f)", 107 rPos.getX(), 108 rPos.getY() ); 109 110 const double fAlpha( getAlpha() ); 111 112 if( isActive() && 113 !::basegfx::fTools::equalZero( fAlpha ) ) 114 { 115 const Point aEmptyPoint; 116 const ::basegfx::B2DVector& rOrigOutputSize( getSizePixel() ); 117 118 // might get changed below (e.g. adapted for 119 // transformations). IMPORTANT: both position and size are 120 // rounded to integer values. From now on, only those 121 // rounded values are used, to keep clip and content in 122 // sync. 123 ::Size aOutputSize( ::vcl::unotools::sizeFromB2DSize( rOrigOutputSize ) ); 124 ::Point aOutPos( ::vcl::unotools::pointFromB2DPoint( rPos ) ); 125 126 127 // TODO(F3): Support for alpha-VDev 128 129 // Do we have to update our bitmaps (necessary if virdev 130 // was painted to, or transformation changed)? 131 const bool bNeedBitmapUpdate( io_bSurfacesDirty || 132 hasTransformChanged() || 133 maContent->IsEmpty() ); 134 135 // updating content of sprite cache - surface is no 136 // longer dirty in relation to our cache 137 io_bSurfacesDirty = false; 138 transformUpdated(); 139 140 if( bNeedBitmapUpdate ) 141 { 142 Bitmap aBmp( mpBackBuffer->getOutDev().GetBitmap( aEmptyPoint, 143 aOutputSize ) ); 144 145 if( isContentFullyOpaque() ) 146 { 147 // optimized case: content canvas is fully 148 // opaque. Note: since we retrieved aBmp directly 149 // from an OutDev, it's already a 'display bitmap' 150 // on windows. 151 maContent = BitmapEx( aBmp ); 152 } 153 else 154 { 155 // sprite content might contain alpha, create 156 // BmpEx, then. 157 Bitmap aMask( mpBackBufferMask->getOutDev().GetBitmap( aEmptyPoint, 158 aOutputSize ) ); 159 160 // bitmasks are much faster than alphamasks on some platforms 161 // so convert to bitmask if useful 162 #if defined LINUX || defined FREEBSD || defined NETBSD || defined QUARTZ 163 // #122485# allow more than 1bit masks for Linux and Mac, 164 // but reduce to mono now 165 aMask.MakeMono(255); 166 #else 167 // #122485# assert when mask uses more than 1bit and reduce 168 // to mono 169 if( aMask.GetBitCount() != 1 ) 170 { 171 OSL_ENSURE(false, 172 "CanvasCustomSprite::redraw(): Mask bitmap is not " 173 "monochrome (performance!)"); 174 aMask.MakeMono(255); 175 } 176 #endif 177 178 // Note: since we retrieved aBmp and aMask 179 // directly from an OutDev, it's already a 180 // 'display bitmap' on windows. 181 maContent = BitmapEx( aBmp, aMask ); 182 } 183 } 184 185 ::basegfx::B2DHomMatrix aTransform( getTransformation() ); 186 187 // check whether matrix is "easy" to handle - pure 188 // translations or scales are handled by OutputDevice 189 // alone 190 const bool bIdentityTransform( aTransform.isIdentity() ); 191 192 // make transformation absolute (put sprite to final 193 // output position). Need to happen here, as we also have 194 // to translate the clip polygon 195 aTransform.translate( aOutPos.X(), 196 aOutPos.Y() ); 197 198 if( !bIdentityTransform ) 199 { 200 if( !::basegfx::fTools::equalZero( aTransform.get(0,1) ) || 201 !::basegfx::fTools::equalZero( aTransform.get(1,0) ) ) 202 { 203 // "complex" transformation, employ affine 204 // transformator 205 206 // modify output position, to account for the fact 207 // that transformBitmap() always normalizes its output 208 // bitmap into the smallest enclosing box. 209 ::basegfx::B2DRectangle aDestRect; 210 ::canvas::tools::calcTransformedRectBounds( aDestRect, 211 ::basegfx::B2DRectangle(0, 212 0, 213 rOrigOutputSize.getX(), 214 rOrigOutputSize.getY()), 215 aTransform ); 216 217 aOutPos.X() = ::basegfx::fround( aDestRect.getMinX() ); 218 aOutPos.Y() = ::basegfx::fround( aDestRect.getMinY() ); 219 220 // TODO(P3): Use optimized bitmap transformation here. 221 222 // actually re-create the bitmap ONLY if necessary 223 if( bNeedBitmapUpdate ) 224 maContent = tools::transformBitmap( *maContent, 225 aTransform, 226 uno::Sequence<double>(), 227 tools::MODULATE_NONE ); 228 229 aOutputSize = maContent->GetSizePixel(); 230 } 231 else 232 { 233 // relatively 'simplistic' transformation - 234 // retrieve scale and translational offset 235 aOutputSize.setWidth ( 236 ::basegfx::fround( rOrigOutputSize.getX() * aTransform.get(0,0) ) ); 237 aOutputSize.setHeight( 238 ::basegfx::fround( rOrigOutputSize.getY() * aTransform.get(1,1) ) ); 239 240 aOutPos.X() = ::basegfx::fround( aTransform.get(0,2) ); 241 aOutPos.Y() = ::basegfx::fround( aTransform.get(1,2) ); 242 } 243 } 244 245 // transformBitmap() might return empty bitmaps, for tiny 246 // scales. 247 if( !!(*maContent) ) 248 { 249 // when true, fast path for slide transition has 250 // already redrawn the sprite. 251 bool bSpriteRedrawn( false ); 252 253 rTargetSurface.Push( PUSH_CLIPREGION ); 254 255 // apply clip (if any) 256 if( getClip().is() ) 257 { 258 ::basegfx::B2DPolyPolygon aClipPoly( 259 ::basegfx::unotools::b2DPolyPolygonFromXPolyPolygon2D( 260 getClip() )); 261 262 if( aClipPoly.count() ) 263 { 264 // aTransform already contains the 265 // translational component, moving the clip to 266 // the final sprite output position. 267 aClipPoly.transform( aTransform ); 268 269 #if ! defined WNT && ! defined QUARTZ 270 // non-Windows only - bAtLeastOnePolygon is 271 // only used in non-WNT code below 272 273 // check whether maybe the clip consists 274 // solely out of rectangular polygons. If this 275 // is the case, enforce using the triangle 276 // clip region setup - non-optimized X11 277 // drivers tend to perform abyssmally on 278 // XPolygonRegion, which is used internally, 279 // when filling complex polypolygons. 280 bool bAtLeastOnePolygon( false ); 281 const sal_Int32 nPolygons( aClipPoly.count() ); 282 283 for( sal_Int32 i=0; i<nPolygons; ++i ) 284 { 285 if( !::basegfx::tools::isRectangle( 286 aClipPoly.getB2DPolygon(i)) ) 287 { 288 bAtLeastOnePolygon = true; 289 break; 290 } 291 } 292 #endif 293 294 if( mbShowSpriteBounds ) 295 { 296 // Paint green sprite clip area 297 rTargetSurface.SetLineColor( Color( 0,255,0 ) ); 298 rTargetSurface.SetFillColor(); 299 300 rTargetSurface.DrawPolyPolygon(PolyPolygon(aClipPoly)); // #i76339# 301 } 302 303 #if ! defined WNT && ! defined QUARTZ 304 // as a matter of fact, this fast path only 305 // performs well for X11 - under Windows, the 306 // clip via SetTriangleClipRegion is faster. 307 if( bAtLeastOnePolygon && 308 bBufferedUpdate && 309 ::rtl::math::approxEqual(fAlpha, 1.0) && 310 !maContent->IsTransparent() ) 311 { 312 // fast path for slide transitions 313 // (buffered, no alpha, no mask (because 314 // full slide is contained in the sprite)) 315 316 // XOR bitmap onto backbuffer, clear area 317 // that should be _visible_ with black, 318 // XOR bitmap again on top of that - 319 // result: XOR cancels out where no black 320 // has been rendered, and yields the 321 // original bitmap, where black is 322 // underneath. 323 rTargetSurface.Push( PUSH_RASTEROP ); 324 rTargetSurface.SetRasterOp( ROP_XOR ); 325 rTargetSurface.DrawBitmap( aOutPos, 326 aOutputSize, 327 maContent->GetBitmap() ); 328 329 rTargetSurface.SetLineColor(); 330 rTargetSurface.SetFillColor( COL_BLACK ); 331 rTargetSurface.SetRasterOp( ROP_0 ); 332 rTargetSurface.DrawPolyPolygon(PolyPolygon(aClipPoly)); // #i76339# 333 334 rTargetSurface.SetRasterOp( ROP_XOR ); 335 rTargetSurface.DrawBitmap( aOutPos, 336 aOutputSize, 337 maContent->GetBitmap() ); 338 339 rTargetSurface.Pop(); 340 341 bSpriteRedrawn = true; 342 } 343 else 344 #endif 345 { 346 Region aClipRegion( aClipPoly ); 347 rTargetSurface.SetClipRegion( aClipRegion ); 348 } 349 } 350 } 351 352 if( !bSpriteRedrawn ) 353 { 354 if( ::rtl::math::approxEqual(fAlpha, 1.0) ) 355 { 356 // no alpha modulation -> just copy to output 357 if( maContent->IsTransparent() ) 358 rTargetSurface.DrawBitmapEx( aOutPos, aOutputSize, *maContent ); 359 else 360 rTargetSurface.DrawBitmap( aOutPos, aOutputSize, maContent->GetBitmap() ); 361 } 362 else 363 { 364 // TODO(P3): Switch to OutputDevice::DrawTransparent() 365 // here 366 367 // draw semi-transparent 368 sal_uInt8 nColor( static_cast<sal_uInt8>( ::basegfx::fround( 255.0*(1.0 - fAlpha) + .5) ) ); 369 AlphaMask aAlpha( maContent->GetSizePixel(), 370 &nColor ); 371 372 // mask out fully transparent areas 373 if( maContent->IsTransparent() ) 374 aAlpha.Replace( maContent->GetMask(), 255 ); 375 376 // alpha-blend to output 377 rTargetSurface.DrawBitmapEx( aOutPos, aOutputSize, 378 BitmapEx( maContent->GetBitmap(), 379 aAlpha ) ); 380 } 381 } 382 383 rTargetSurface.Pop(); 384 385 if( mbShowSpriteBounds ) 386 { 387 ::PolyPolygon aMarkerPoly( 388 ::canvas::tools::getBoundMarksPolyPolygon( 389 ::basegfx::B2DRectangle(aOutPos.X(), 390 aOutPos.Y(), 391 aOutPos.X() + aOutputSize.Width()-1, 392 aOutPos.Y() + aOutputSize.Height()-1) ) ); 393 394 // Paint little red sprite area markers 395 rTargetSurface.SetLineColor( COL_RED ); 396 rTargetSurface.SetFillColor(); 397 398 for( int i=0; i<aMarkerPoly.Count(); ++i ) 399 { 400 rTargetSurface.DrawPolyLine( aMarkerPoly.GetObject((sal_uInt16)i) ); 401 } 402 403 // paint sprite prio 404 Font aVCLFont; 405 aVCLFont.SetHeight( std::min(long(20),aOutputSize.Height()) ); 406 aVCLFont.SetColor( COL_RED ); 407 408 rTargetSurface.SetTextAlign(ALIGN_TOP); 409 rTargetSurface.SetTextColor( COL_RED ); 410 rTargetSurface.SetFont( aVCLFont ); 411 412 ::rtl::OUString text( ::rtl::math::doubleToUString( getPriority(), 413 rtl_math_StringFormat_F, 414 2,'.',NULL,' ') ); 415 416 rTargetSurface.DrawText( aOutPos+Point(2,2), text ); 417 418 #if defined(VERBOSE) && OSL_DEBUG_LEVEL > 0 419 OSL_TRACE( "SpriteHelper::redraw(): sprite %X has prio %f\n", 420 this, getPriority() ); 421 #endif 422 } 423 } 424 } 425 } 426 polyPolygonFromXPolyPolygon2D(uno::Reference<rendering::XPolyPolygon2D> & xPoly) const427 ::basegfx::B2DPolyPolygon SpriteHelper::polyPolygonFromXPolyPolygon2D( uno::Reference< rendering::XPolyPolygon2D >& xPoly ) const 428 { 429 return ::basegfx::unotools::b2DPolyPolygonFromXPolyPolygon2D( xPoly ); 430 } 431 432 } 433