xref: /aoo42x/main/canvas/source/vcl/spritehelper.cxx (revision 25ea7f45)
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