1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ 2 /* 3 * This file is part of the LibreOffice project. 4 * 5 * This Source Code Form is subject to the terms of the Mozilla Public 6 * License, v. 2.0. If a copy of the MPL was not distributed with this 7 * file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 * 9 * This file incorporates work covered by the following license notice: 10 * 11 * Licensed to the Apache Software Foundation (ASF) under one or more 12 * contributor license agreements. See the NOTICE file distributed 13 * with this work for additional information regarding copyright 14 * ownership. The ASF licenses this file to you under the Apache 15 * License, Version 2.0 (the "License"); you may not use this file 16 * except in compliance with the License. You may obtain a copy of 17 * the License at http://www.apache.org/licenses/LICENSE-2.0 . 18 */ 19 20 #include <sal/config.h> 21 #include <sal/log.hxx> 22 23 #include <basegfx/matrix/b2dhommatrix.hxx> 24 #include <basegfx/numeric/ftools.hxx> 25 #include <basegfx/point/b2dpoint.hxx> 26 #include <basegfx/range/b2drectangle.hxx> 27 #include <basegfx/utils/canvastools.hxx> 28 #include <rtl/math.hxx> 29 #include <tools/diagnose_ex.h> 30 #include <vcl/alpha.hxx> 31 #include <vcl/bitmapex.hxx> 32 #include <vcl/canvastools.hxx> 33 #include <vcl/outdev.hxx> 34 #include <vcl/BitmapMonochromeFilter.hxx> 35 #include <vcl/opengl/OpenGLHelper.hxx> 36 37 #include <canvas/canvastools.hxx> 38 #include <config_features.h> 39 40 #include "impltools.hxx" 41 #include "spritehelper.hxx" 42 43 using namespace ::com::sun::star; 44 45 46 namespace vclcanvas 47 { SpriteHelper()48 SpriteHelper::SpriteHelper() : 49 mpBackBuffer(), 50 mpBackBufferMask(), 51 maContent(), 52 mbShowSpriteBounds(false) 53 { 54 } 55 init(const geometry::RealSize2D & rSpriteSize,const::canvas::SpriteSurface::Reference & rOwningSpriteCanvas,const BackBufferSharedPtr & rBackBuffer,const BackBufferSharedPtr & rBackBufferMask,bool bShowSpriteBounds)56 void SpriteHelper::init( const geometry::RealSize2D& rSpriteSize, 57 const ::canvas::SpriteSurface::Reference& rOwningSpriteCanvas, 58 const BackBufferSharedPtr& rBackBuffer, 59 const BackBufferSharedPtr& rBackBufferMask, 60 bool bShowSpriteBounds ) 61 { 62 ENSURE_OR_THROW( rOwningSpriteCanvas.get() && rBackBuffer && rBackBufferMask, 63 "SpriteHelper::init(): Invalid sprite canvas or back buffer" ); 64 65 mpBackBuffer = rBackBuffer; 66 mpBackBufferMask = rBackBufferMask; 67 mbShowSpriteBounds = bShowSpriteBounds; 68 69 init( rSpriteSize, rOwningSpriteCanvas ); 70 } 71 disposing()72 void SpriteHelper::disposing() 73 { 74 mpBackBuffer.reset(); 75 mpBackBufferMask.reset(); 76 77 // forward to parent 78 CanvasCustomSpriteHelper::disposing(); 79 } 80 redraw(OutputDevice & rTargetSurface,const::basegfx::B2DPoint & rPos,bool & io_bSurfacesDirty,bool bBufferedUpdate) const81 void SpriteHelper::redraw( OutputDevice& rTargetSurface, 82 const ::basegfx::B2DPoint& rPos, 83 bool& io_bSurfacesDirty, 84 bool bBufferedUpdate ) const 85 { 86 (void)bBufferedUpdate; // not used on every platform 87 88 if( !mpBackBuffer || 89 !mpBackBufferMask ) 90 { 91 return; // we're disposed 92 } 93 94 // log output pos in device pixel 95 SAL_INFO("canvas.vcl", "SpriteHelper::redraw(): output pos is (" << 96 rPos.getX() << "," << rPos.getY() << ")"); 97 98 const double fAlpha( getAlpha() ); 99 100 if( isActive() && 101 !::basegfx::fTools::equalZero( fAlpha ) ) 102 { 103 const Point aEmptyPoint; 104 const ::basegfx::B2DVector& rOrigOutputSize( getSizePixel() ); 105 106 // might get changed below (e.g. adapted for 107 // transformations). IMPORTANT: both position and size are 108 // rounded to integer values. From now on, only those 109 // rounded values are used, to keep clip and content in 110 // sync. 111 ::Size aOutputSize( vcl::unotools::sizeFromB2DSize( rOrigOutputSize ) ); 112 ::Point aOutPos( vcl::unotools::pointFromB2DPoint( rPos ) ); 113 114 115 // TODO(F3): Support for alpha-VDev 116 117 // Do we have to update our bitmaps (necessary if virdev 118 // was painted to, or transformation changed)? 119 const bool bNeedBitmapUpdate( io_bSurfacesDirty || 120 hasTransformChanged() || 121 maContent->IsEmpty() ); 122 123 // updating content of sprite cache - surface is no 124 // longer dirty in relation to our cache 125 io_bSurfacesDirty = false; 126 transformUpdated(); 127 128 if( bNeedBitmapUpdate ) 129 { 130 BitmapEx aBmp( mpBackBuffer->getOutDev().GetBitmapEx( aEmptyPoint, 131 aOutputSize ) ); 132 133 if( isContentFullyOpaque() ) 134 { 135 // optimized case: content canvas is fully 136 // opaque. Note: since we retrieved aBmp directly 137 // from an OutDev, it's already a 'display bitmap' 138 // on windows. 139 maContent = aBmp; 140 } 141 else 142 { 143 // sprite content might contain alpha, create 144 // BmpEx, then. 145 BitmapEx aMask( mpBackBufferMask->getOutDev().GetBitmapEx( aEmptyPoint, 146 aOutputSize ) ); 147 148 // bitmasks are much faster than alphamasks on some platforms 149 // so convert to bitmask if useful 150 #ifndef MACOSX 151 if( aMask.GetBitCount() != 1 ) 152 { 153 OSL_FAIL("CanvasCustomSprite::redraw(): Mask bitmap is not " 154 "monochrome (performance!)"); 155 BitmapEx aMaskEx(aMask); 156 BitmapFilter::Filter(aMaskEx, BitmapMonochromeFilter(255)); 157 aMask = aMaskEx.GetBitmap(); 158 } 159 #endif 160 161 // Note: since we retrieved aBmp and aMask 162 // directly from an OutDev, it's already a 163 // 'display bitmap' on windows. 164 maContent = BitmapEx( aBmp.GetBitmap(), aMask.GetBitmap() ); 165 } 166 } 167 168 ::basegfx::B2DHomMatrix aTransform( getTransformation() ); 169 170 // check whether matrix is "easy" to handle - pure 171 // translations or scales are handled by OutputDevice 172 // alone 173 const bool bIdentityTransform( aTransform.isIdentity() ); 174 175 // make transformation absolute (put sprite to final 176 // output position). Need to happen here, as we also have 177 // to translate the clip polygon 178 aTransform.translate( aOutPos.X(), 179 aOutPos.Y() ); 180 181 if( !bIdentityTransform ) 182 { 183 // Avoid the trick with the negative width in the OpenGL case, 184 // OutputDevice::DrawDeviceAlphaBitmap() doesn't like it. 185 if (!::basegfx::fTools::equalZero( aTransform.get(0,1) ) || 186 !::basegfx::fTools::equalZero( aTransform.get(1,0) ) 187 #if HAVE_FEATURE_UI 188 || OpenGLHelper::isVCLOpenGLEnabled() 189 #endif 190 ) 191 { 192 // "complex" transformation, employ affine 193 // transformator 194 195 // modify output position, to account for the fact 196 // that transformBitmap() always normalizes its output 197 // bitmap into the smallest enclosing box. 198 ::basegfx::B2DRectangle aDestRect; 199 ::canvas::tools::calcTransformedRectBounds( aDestRect, 200 ::basegfx::B2DRectangle(0, 201 0, 202 rOrigOutputSize.getX(), 203 rOrigOutputSize.getY()), 204 aTransform ); 205 206 aOutPos.setX( ::basegfx::fround( aDestRect.getMinX() ) ); 207 aOutPos.setY( ::basegfx::fround( aDestRect.getMinY() ) ); 208 209 // TODO(P3): Use optimized bitmap transformation here. 210 211 // actually re-create the bitmap ONLY if necessary 212 if( bNeedBitmapUpdate ) 213 maContent = tools::transformBitmap( *maContent, 214 aTransform ); 215 216 aOutputSize = maContent->GetSizePixel(); 217 } 218 else 219 { 220 // relatively 'simplistic' transformation - 221 // retrieve scale and translational offset 222 aOutputSize.setWidth ( 223 ::basegfx::fround( rOrigOutputSize.getX() * aTransform.get(0,0) ) ); 224 aOutputSize.setHeight( 225 ::basegfx::fround( rOrigOutputSize.getY() * aTransform.get(1,1) ) ); 226 227 aOutPos.setX( ::basegfx::fround( aTransform.get(0,2) ) ); 228 aOutPos.setY( ::basegfx::fround( aTransform.get(1,2) ) ); 229 } 230 } 231 232 // transformBitmap() might return empty bitmaps, for tiny 233 // scales. 234 if( !!(*maContent) ) 235 { 236 rTargetSurface.Push( PushFlags::CLIPREGION ); 237 238 // apply clip (if any) 239 if( getClip().is() ) 240 { 241 ::basegfx::B2DPolyPolygon aClipPoly( 242 ::basegfx::unotools::b2DPolyPolygonFromXPolyPolygon2D( 243 getClip() )); 244 245 if( aClipPoly.count() ) 246 { 247 // aTransform already contains the 248 // translational component, moving the clip to 249 // the final sprite output position. 250 aClipPoly.transform( aTransform ); 251 252 if( mbShowSpriteBounds ) 253 { 254 // Paint green sprite clip area 255 rTargetSurface.SetLineColor( Color( 0,255,0 ) ); 256 rTargetSurface.SetFillColor(); 257 258 rTargetSurface.DrawPolyPolygon(::tools::PolyPolygon(aClipPoly)); // #i76339# 259 } 260 261 vcl::Region aClipRegion( aClipPoly ); 262 rTargetSurface.SetClipRegion( aClipRegion ); 263 } 264 } 265 266 if( ::rtl::math::approxEqual(fAlpha, 1.0) ) 267 { 268 // no alpha modulation -> just copy to output 269 if( maContent->IsTransparent() ) 270 rTargetSurface.DrawBitmapEx( aOutPos, aOutputSize, *maContent ); 271 else 272 rTargetSurface.DrawBitmap( aOutPos, aOutputSize, maContent->GetBitmap() ); 273 } 274 else 275 { 276 // TODO(P3): Switch to OutputDevice::DrawTransparent() 277 // here 278 279 // draw semi-transparent 280 sal_uInt8 nColor( static_cast<sal_uInt8>( ::basegfx::fround( 255.0*(1.0 - fAlpha) + .5) ) ); 281 AlphaMask aAlpha( maContent->GetSizePixel(), 282 &nColor ); 283 284 // mask out fully transparent areas 285 if( maContent->IsTransparent() ) 286 aAlpha.Replace( maContent->GetMask(), 255 ); 287 288 // alpha-blend to output 289 rTargetSurface.DrawBitmapEx( aOutPos, aOutputSize, 290 BitmapEx( maContent->GetBitmap(), 291 aAlpha ) ); 292 } 293 294 rTargetSurface.Pop(); 295 296 if( mbShowSpriteBounds ) 297 { 298 ::tools::PolyPolygon aMarkerPoly( 299 ::canvas::tools::getBoundMarksPolyPolygon( 300 ::basegfx::B2DRectangle(aOutPos.X(), 301 aOutPos.Y(), 302 aOutPos.X() + aOutputSize.Width()-1, 303 aOutPos.Y() + aOutputSize.Height()-1) ) ); 304 305 // Paint little red sprite area markers 306 rTargetSurface.SetLineColor( COL_RED ); 307 rTargetSurface.SetFillColor(); 308 309 for( int i=0; i<aMarkerPoly.Count(); ++i ) 310 { 311 rTargetSurface.DrawPolyLine( aMarkerPoly.GetObject(static_cast<sal_uInt16>(i)) ); 312 } 313 314 // paint sprite prio 315 vcl::Font aVCLFont; 316 aVCLFont.SetFontHeight( std::min(long(20),aOutputSize.Height()) ); 317 aVCLFont.SetColor( COL_RED ); 318 319 rTargetSurface.SetTextAlign(ALIGN_TOP); 320 rTargetSurface.SetTextColor( COL_RED ); 321 rTargetSurface.SetFont( aVCLFont ); 322 323 OUString text( ::rtl::math::doubleToUString( getPriority(), 324 rtl_math_StringFormat_F, 325 2,'.',nullptr,' ') ); 326 327 rTargetSurface.DrawText( aOutPos+Point(2,2), text ); 328 SAL_INFO( "canvas.vcl", 329 "sprite " << this << " has prio " << getPriority()); 330 } 331 } 332 } 333 } 334 polyPolygonFromXPolyPolygon2D(uno::Reference<rendering::XPolyPolygon2D> & xPoly) const335 ::basegfx::B2DPolyPolygon SpriteHelper::polyPolygonFromXPolyPolygon2D( uno::Reference< rendering::XPolyPolygon2D >& xPoly ) const 336 { 337 return ::basegfx::unotools::b2DPolyPolygonFromXPolyPolygon2D( xPoly ); 338 } 339 340 } 341 342 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ 343