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 { 59 SpriteHelper::SpriteHelper() : 60 mpBackBuffer(), 61 mpBackBufferMask(), 62 maContent(), 63 mbShowSpriteBounds(false) 64 { 65 } 66 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 83 void SpriteHelper::disposing() 84 { 85 mpBackBuffer.reset(); 86 mpBackBufferMask.reset(); 87 88 // forward to parent 89 CanvasCustomSpriteHelper::disposing(); 90 } 91 92 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 #ifndef QUARTZ 163 if( aMask.GetBitCount() != 1 ) 164 { 165 OSL_ENSURE(false, 166 "CanvasCustomSprite::redraw(): Mask bitmap is not " 167 "monochrome (performance!)"); 168 aMask.MakeMono(255); 169 } 170 #endif 171 172 // Note: since we retrieved aBmp and aMask 173 // directly from an OutDev, it's already a 174 // 'display bitmap' on windows. 175 maContent = BitmapEx( aBmp, aMask ); 176 } 177 } 178 179 ::basegfx::B2DHomMatrix aTransform( getTransformation() ); 180 181 // check whether matrix is "easy" to handle - pure 182 // translations or scales are handled by OutputDevice 183 // alone 184 const bool bIdentityTransform( aTransform.isIdentity() ); 185 186 // make transformation absolute (put sprite to final 187 // output position). Need to happen here, as we also have 188 // to translate the clip polygon 189 aTransform.translate( aOutPos.X(), 190 aOutPos.Y() ); 191 192 if( !bIdentityTransform ) 193 { 194 if( !::basegfx::fTools::equalZero( aTransform.get(0,1) ) || 195 !::basegfx::fTools::equalZero( aTransform.get(1,0) ) ) 196 { 197 // "complex" transformation, employ affine 198 // transformator 199 200 // modify output position, to account for the fact 201 // that transformBitmap() always normalizes its output 202 // bitmap into the smallest enclosing box. 203 ::basegfx::B2DRectangle aDestRect; 204 ::canvas::tools::calcTransformedRectBounds( aDestRect, 205 ::basegfx::B2DRectangle(0, 206 0, 207 rOrigOutputSize.getX(), 208 rOrigOutputSize.getY()), 209 aTransform ); 210 211 aOutPos.X() = ::basegfx::fround( aDestRect.getMinX() ); 212 aOutPos.Y() = ::basegfx::fround( aDestRect.getMinY() ); 213 214 // TODO(P3): Use optimized bitmap transformation here. 215 216 // actually re-create the bitmap ONLY if necessary 217 if( bNeedBitmapUpdate ) 218 maContent = tools::transformBitmap( *maContent, 219 aTransform, 220 uno::Sequence<double>(), 221 tools::MODULATE_NONE ); 222 223 aOutputSize = maContent->GetSizePixel(); 224 } 225 else 226 { 227 // relatively 'simplistic' transformation - 228 // retrieve scale and translational offset 229 aOutputSize.setWidth ( 230 ::basegfx::fround( rOrigOutputSize.getX() * aTransform.get(0,0) ) ); 231 aOutputSize.setHeight( 232 ::basegfx::fround( rOrigOutputSize.getY() * aTransform.get(1,1) ) ); 233 234 aOutPos.X() = ::basegfx::fround( aTransform.get(0,2) ); 235 aOutPos.Y() = ::basegfx::fround( aTransform.get(1,2) ); 236 } 237 } 238 239 // transformBitmap() might return empty bitmaps, for tiny 240 // scales. 241 if( !!(*maContent) ) 242 { 243 // when true, fast path for slide transition has 244 // already redrawn the sprite. 245 bool bSpriteRedrawn( false ); 246 247 rTargetSurface.Push( PUSH_CLIPREGION ); 248 249 // apply clip (if any) 250 if( getClip().is() ) 251 { 252 ::basegfx::B2DPolyPolygon aClipPoly( 253 ::basegfx::unotools::b2DPolyPolygonFromXPolyPolygon2D( 254 getClip() )); 255 256 if( aClipPoly.count() ) 257 { 258 // aTransform already contains the 259 // translational component, moving the clip to 260 // the final sprite output position. 261 aClipPoly.transform( aTransform ); 262 263 #if ! defined WNT && ! defined QUARTZ 264 // non-Windows only - bAtLeastOnePolygon is 265 // only used in non-WNT code below 266 267 // check whether maybe the clip consists 268 // solely out of rectangular polygons. If this 269 // is the case, enforce using the triangle 270 // clip region setup - non-optimized X11 271 // drivers tend to perform abyssmally on 272 // XPolygonRegion, which is used internally, 273 // when filling complex polypolygons. 274 bool bAtLeastOnePolygon( false ); 275 const sal_Int32 nPolygons( aClipPoly.count() ); 276 277 for( sal_Int32 i=0; i<nPolygons; ++i ) 278 { 279 if( !::basegfx::tools::isRectangle( 280 aClipPoly.getB2DPolygon(i)) ) 281 { 282 bAtLeastOnePolygon = true; 283 break; 284 } 285 } 286 #endif 287 288 if( mbShowSpriteBounds ) 289 { 290 // Paint green sprite clip area 291 rTargetSurface.SetLineColor( Color( 0,255,0 ) ); 292 rTargetSurface.SetFillColor(); 293 294 rTargetSurface.DrawPolyPolygon(PolyPolygon(aClipPoly)); // #i76339# 295 } 296 297 #if ! defined WNT && ! defined QUARTZ 298 // as a matter of fact, this fast path only 299 // performs well for X11 - under Windows, the 300 // clip via SetTriangleClipRegion is faster. 301 if( bAtLeastOnePolygon && 302 bBufferedUpdate && 303 ::rtl::math::approxEqual(fAlpha, 1.0) && 304 !maContent->IsTransparent() ) 305 { 306 // fast path for slide transitions 307 // (buffered, no alpha, no mask (because 308 // full slide is contained in the sprite)) 309 310 // XOR bitmap onto backbuffer, clear area 311 // that should be _visible_ with black, 312 // XOR bitmap again on top of that - 313 // result: XOR cancels out where no black 314 // has been rendered, and yields the 315 // original bitmap, where black is 316 // underneath. 317 rTargetSurface.Push( PUSH_RASTEROP ); 318 rTargetSurface.SetRasterOp( ROP_XOR ); 319 rTargetSurface.DrawBitmap( aOutPos, 320 aOutputSize, 321 maContent->GetBitmap() ); 322 323 rTargetSurface.SetLineColor(); 324 rTargetSurface.SetFillColor( COL_BLACK ); 325 rTargetSurface.SetRasterOp( ROP_0 ); 326 rTargetSurface.DrawPolyPolygon(PolyPolygon(aClipPoly)); // #i76339# 327 328 rTargetSurface.SetRasterOp( ROP_XOR ); 329 rTargetSurface.DrawBitmap( aOutPos, 330 aOutputSize, 331 maContent->GetBitmap() ); 332 333 rTargetSurface.Pop(); 334 335 bSpriteRedrawn = true; 336 } 337 else 338 #endif 339 { 340 Region aClipRegion( aClipPoly ); 341 rTargetSurface.SetClipRegion( aClipRegion ); 342 } 343 } 344 } 345 346 if( !bSpriteRedrawn ) 347 { 348 if( ::rtl::math::approxEqual(fAlpha, 1.0) ) 349 { 350 // no alpha modulation -> just copy to output 351 if( maContent->IsTransparent() ) 352 rTargetSurface.DrawBitmapEx( aOutPos, aOutputSize, *maContent ); 353 else 354 rTargetSurface.DrawBitmap( aOutPos, aOutputSize, maContent->GetBitmap() ); 355 } 356 else 357 { 358 // TODO(P3): Switch to OutputDevice::DrawTransparent() 359 // here 360 361 // draw semi-transparent 362 sal_uInt8 nColor( static_cast<sal_uInt8>( ::basegfx::fround( 255.0*(1.0 - fAlpha) + .5) ) ); 363 AlphaMask aAlpha( maContent->GetSizePixel(), 364 &nColor ); 365 366 // mask out fully transparent areas 367 if( maContent->IsTransparent() ) 368 aAlpha.Replace( maContent->GetMask(), 255 ); 369 370 // alpha-blend to output 371 rTargetSurface.DrawBitmapEx( aOutPos, aOutputSize, 372 BitmapEx( maContent->GetBitmap(), 373 aAlpha ) ); 374 } 375 } 376 377 rTargetSurface.Pop(); 378 379 if( mbShowSpriteBounds ) 380 { 381 ::PolyPolygon aMarkerPoly( 382 ::canvas::tools::getBoundMarksPolyPolygon( 383 ::basegfx::B2DRectangle(aOutPos.X(), 384 aOutPos.Y(), 385 aOutPos.X() + aOutputSize.Width()-1, 386 aOutPos.Y() + aOutputSize.Height()-1) ) ); 387 388 // Paint little red sprite area markers 389 rTargetSurface.SetLineColor( COL_RED ); 390 rTargetSurface.SetFillColor(); 391 392 for( int i=0; i<aMarkerPoly.Count(); ++i ) 393 { 394 rTargetSurface.DrawPolyLine( aMarkerPoly.GetObject((sal_uInt16)i) ); 395 } 396 397 // paint sprite prio 398 Font aVCLFont; 399 aVCLFont.SetHeight( std::min(long(20),aOutputSize.Height()) ); 400 aVCLFont.SetColor( COL_RED ); 401 402 rTargetSurface.SetTextAlign(ALIGN_TOP); 403 rTargetSurface.SetTextColor( COL_RED ); 404 rTargetSurface.SetFont( aVCLFont ); 405 406 ::rtl::OUString text( ::rtl::math::doubleToUString( getPriority(), 407 rtl_math_StringFormat_F, 408 2,'.',NULL,' ') ); 409 410 rTargetSurface.DrawText( aOutPos+Point(2,2), text ); 411 412 #if defined(VERBOSE) && OSL_DEBUG_LEVEL > 0 413 OSL_TRACE( "SpriteHelper::redraw(): sprite %X has prio %f\n", 414 this, getPriority() ); 415 #endif 416 } 417 } 418 } 419 } 420 421 ::basegfx::B2DPolyPolygon SpriteHelper::polyPolygonFromXPolyPolygon2D( uno::Reference< rendering::XPolyPolygon2D >& xPoly ) const 422 { 423 return ::basegfx::unotools::b2DPolyPolygonFromXPolyPolygon2D( xPoly ); 424 } 425 426 } 427