xref: /trunk/main/canvas/source/vcl/spritehelper.cxx (revision 76e9555f)
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