1 /*************************************************************************
2  *
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * Copyright 2000, 2010 Oracle and/or its affiliates.
6  *
7  * OpenOffice.org - a multi-platform office productivity suite
8  *
9  * This file is part of OpenOffice.org.
10  *
11  * OpenOffice.org is free software: you can redistribute it and/or modify
12  * it under the terms of the GNU Lesser General Public License version 3
13  * only, as published by the Free Software Foundation.
14  *
15  * OpenOffice.org is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU Lesser General Public License version 3 for more details
19  * (a copy is included in the LICENSE file that accompanied this code).
20  *
21  * You should have received a copy of the GNU Lesser General Public License
22  * version 3 along with OpenOffice.org.  If not, see
23  * <http://www.openoffice.org/license.html>
24  * for a copy of the LGPLv3 License.
25  *
26  ************************************************************************/
27 
28 // MARKER(update_precomp.py): autogen include statement, do not remove
29 #include "precompiled_canvas.hxx"
30 
31 #include <canvas/debug.hxx>
32 #include <tools/diagnose_ex.h>
33 #include <canvas/verbosetrace.hxx>
34 #include <canvas/canvastools.hxx>
35 
36 #include <rtl/math.hxx>
37 
38 #include <basegfx/matrix/b2dhommatrix.hxx>
39 #include <basegfx/point/b2dpoint.hxx>
40 #include <basegfx/tools/canvastools.hxx>
41 #include <basegfx/polygon/b2dpolygon.hxx>
42 #include <basegfx/polygon/b2dpolygontools.hxx>
43 #include <basegfx/polygon/b2dpolypolygontools.hxx>
44 #include <basegfx/numeric/ftools.hxx>
45 
46 #include <canvas/base/canvascustomspritehelper.hxx>
47 
48 using namespace ::com::sun::star;
49 
50 
51 namespace canvas
52 {
53     bool CanvasCustomSpriteHelper::updateClipState( const Sprite::Reference& rSprite )
54     {
55         if( !mxClipPoly.is() )
56         {
57             // empty clip polygon -> everything is visible now
58             maCurrClipBounds.reset();
59             mbIsCurrClipRectangle = true;
60         }
61         else
62         {
63             const sal_Int32 nNumClipPolygons( mxClipPoly->getNumberOfPolygons() );
64 
65             // clip is not empty - determine actual update area
66             ::basegfx::B2DPolyPolygon aClipPath(
67                 polyPolygonFromXPolyPolygon2D( mxClipPoly ) );
68 
69             // apply sprite transformation also to clip!
70             aClipPath.transform( maTransform );
71 
72             // clip which is about to be set, expressed as a
73             // b2drectangle
74             const ::basegfx::B2DRectangle& rClipBounds(
75                 ::basegfx::tools::getRange( aClipPath ) );
76 
77             const ::basegfx::B2DRectangle aBounds( 0.0, 0.0,
78                                                    maSize.getX(),
79                                                    maSize.getY() );
80 
81             // rectangular area which is actually covered by the sprite.
82             // coordinates are relative to the sprite origin.
83             ::basegfx::B2DRectangle aSpriteRectPixel;
84             ::canvas::tools::calcTransformedRectBounds( aSpriteRectPixel,
85                                                         aBounds,
86                                                         maTransform );
87 
88             // aClipBoundsA = new clip bound rect, intersected
89             // with sprite area
90             ::basegfx::B2DRectangle aClipBoundsA(rClipBounds);
91             aClipBoundsA.intersect( aSpriteRectPixel );
92 
93             if( nNumClipPolygons != 1 )
94             {
95                 // clip cannot be a single rectangle -> cannot
96                 // optimize update
97                 mbIsCurrClipRectangle = false;
98                 maCurrClipBounds = aClipBoundsA;
99             }
100             else
101             {
102                 // new clip could be a single rectangle - check
103                 // that now:
104                 const bool bNewClipIsRect(
105                     ::basegfx::tools::isRectangle( aClipPath.getB2DPolygon(0) ) );
106 
107                 // both new and old clip are truly rectangles
108                 // - can now take the optimized path
109                 const bool bUseOptimizedUpdate( bNewClipIsRect &&
110                                                 mbIsCurrClipRectangle );
111 
112                 const ::basegfx::B2DRectangle aOldBounds( maCurrClipBounds );
113 
114                 // store new current clip type
115                 maCurrClipBounds = aClipBoundsA;
116                 mbIsCurrClipRectangle = bNewClipIsRect;
117 
118                 if( mbActive &&
119                     bUseOptimizedUpdate  )
120                 {
121                     // aClipBoundsB = maCurrClipBounds, i.e. last
122                     // clip, intersected with sprite area
123                     typedef ::std::vector< ::basegfx::B2DRectangle > VectorOfRects;
124                     VectorOfRects aClipDifferences;
125 
126                     // get all rectangles covered by exactly one
127                     // of the polygons (aka XOR)
128                     ::basegfx::computeSetDifference(aClipDifferences,
129                                                     aClipBoundsA,
130                                                     aOldBounds);
131 
132                     // aClipDifferences now contains the final
133                     // update areas, coordinates are still relative
134                     // to the sprite origin. before submitting
135                     // this area to 'updateSprite()' we need to
136                     // translate this area to the final position,
137                     // coordinates need to be relative to the
138                     // spritecanvas.
139                     VectorOfRects::const_iterator 		aCurr( aClipDifferences.begin() );
140                     const VectorOfRects::const_iterator aEnd( aClipDifferences.end() );
141                     while( aCurr != aEnd )
142                     {
143                         mpSpriteCanvas->updateSprite(
144                             rSprite,
145                             maPosition,
146                             ::basegfx::B2DRectangle(
147                                 maPosition + aCurr->getMinimum(),
148                                 maPosition + aCurr->getMaximum() ) );
149                         ++aCurr;
150                     }
151 
152                     // update calls all done
153                     return true;
154                 }
155             }
156         }
157 
158         // caller needs to perform update calls
159         return false;
160     }
161 
162     CanvasCustomSpriteHelper::CanvasCustomSpriteHelper() :
163         mpSpriteCanvas(),
164         maCurrClipBounds(),
165         maPosition(),
166         maSize(),
167         maTransform(),
168         mxClipPoly(),
169         mfPriority(0.0),
170         mfAlpha(0.0),
171         mbActive(false),
172         mbIsCurrClipRectangle(true),
173         mbIsContentFullyOpaque( false ),
174         mbAlphaDirty( true ),
175         mbPositionDirty( true ),
176         mbTransformDirty( true ),
177         mbClipDirty( true ),
178         mbPrioDirty( true ),
179         mbVisibilityDirty( true )
180     {
181     }
182 
183     void CanvasCustomSpriteHelper::init( const geometry::RealSize2D& 		rSpriteSize,
184                                          const SpriteSurface::Reference&	rOwningSpriteCanvas )
185     {
186         ENSURE_OR_THROW( rOwningSpriteCanvas.get(),
187                           "CanvasCustomSpriteHelper::init(): Invalid owning sprite canvas" );
188 
189         mpSpriteCanvas = rOwningSpriteCanvas;
190         maSize.setX( ::std::max( 1.0,
191                                  ceil( rSpriteSize.Width ) ) ); // round up to nearest int,
192                 											 	// enforce sprite to have at
193                 											 	// least (1,1) pixel size
194         maSize.setY( ::std::max( 1.0,
195                                  ceil( rSpriteSize.Height ) ) );
196     }
197 
198     void CanvasCustomSpriteHelper::disposing()
199     {
200         mpSpriteCanvas.clear();
201     }
202 
203     void CanvasCustomSpriteHelper::clearingContent( const Sprite::Reference& /*rSprite*/ )
204     {
205         // about to clear content to fully transparent
206         mbIsContentFullyOpaque = false;
207     }
208 
209     void CanvasCustomSpriteHelper::checkDrawBitmap( const Sprite::Reference& 					rSprite,
210                                                     const uno::Reference< rendering::XBitmap >&	xBitmap,
211                                                     const rendering::ViewState& 				viewState,
212                                                     const rendering::RenderState& 				renderState )
213     {
214         // check whether bitmap is non-alpha, and whether its
215         // transformed size covers the whole sprite.
216         if( !xBitmap->hasAlpha() )
217         {
218             const geometry::IntegerSize2D& rInputSize(
219                 xBitmap->getSize() );
220             const ::basegfx::B2DSize& rOurSize(
221                 rSprite->getSizePixel() );
222 
223             ::basegfx::B2DHomMatrix aTransform;
224             if( tools::isInside(
225                     ::basegfx::B2DRectangle( 0.0,0.0,
226                                              rOurSize.getX(),
227                                              rOurSize.getY() ),
228                     ::basegfx::B2DRectangle( 0.0,0.0,
229                                              rInputSize.Width,
230                                              rInputSize.Height ),
231                     ::canvas::tools::mergeViewAndRenderTransform(aTransform,
232                                                                  viewState,
233                                                                  renderState) ) )
234             {
235                 // bitmap is opaque and will fully cover the sprite,
236                 // set flag appropriately
237                 mbIsContentFullyOpaque = true;
238             }
239         }
240     }
241 
242     void CanvasCustomSpriteHelper::setAlpha( const Sprite::Reference&	rSprite,
243                                              double 					alpha )
244     {
245         if( !mpSpriteCanvas.get() )
246             return; // we're disposed
247 
248         if( alpha != mfAlpha )
249         {
250             mfAlpha = alpha;
251 
252             if( mbActive )
253             {
254                 mpSpriteCanvas->updateSprite( rSprite,
255                                               maPosition,
256                                               getUpdateArea() );
257             }
258 
259             mbAlphaDirty = true;
260         }
261     }
262 
263     void CanvasCustomSpriteHelper::move( const Sprite::Reference&		rSprite,
264                                          const geometry::RealPoint2D&  	aNewPos,
265                                          const rendering::ViewState&   	viewState,
266                                          const rendering::RenderState& 	renderState )
267     {
268         if( !mpSpriteCanvas.get() )
269             return; // we're disposed
270 
271         ::basegfx::B2DHomMatrix aTransform;
272         ::canvas::tools::mergeViewAndRenderTransform(aTransform,
273                                                      viewState,
274                                                      renderState);
275 
276         // convert position to device pixel
277         ::basegfx::B2DPoint aPoint(
278             ::basegfx::unotools::b2DPointFromRealPoint2D(aNewPos) );
279         aPoint *= aTransform;
280 
281         if( aPoint != maPosition )
282         {
283             const ::basegfx::B2DRectangle& 	rBounds( getFullSpriteRect() );
284 
285             if( mbActive )
286             {
287                 mpSpriteCanvas->moveSprite( rSprite,
288                                             rBounds.getMinimum(),
289                                             rBounds.getMinimum() - maPosition + aPoint,
290                                             rBounds.getRange() );
291             }
292 
293             maPosition = aPoint;
294             mbPositionDirty = true;
295         }
296     }
297 
298     void CanvasCustomSpriteHelper::transform( const Sprite::Reference&			rSprite,
299                                               const geometry::AffineMatrix2D&	aTransformation )
300     {
301         ::basegfx::B2DHomMatrix aMatrix;
302 		::basegfx::unotools::homMatrixFromAffineMatrix(aMatrix,
303                                                        aTransformation);
304 
305         if( maTransform != aMatrix )
306         {
307             // retrieve bounds before and after transformation change.
308             const ::basegfx::B2DRectangle& rPrevBounds( getUpdateArea() );
309 
310             maTransform = aMatrix;
311 
312             if( !updateClipState( rSprite ) &&
313                 mbActive )
314             {
315                 mpSpriteCanvas->updateSprite( rSprite,
316                                               maPosition,
317                                               rPrevBounds );
318                 mpSpriteCanvas->updateSprite( rSprite,
319                                               maPosition,
320                                               getUpdateArea() );
321             }
322 
323             mbTransformDirty = true;
324         }
325     }
326 
327     void CanvasCustomSpriteHelper::clip( const Sprite::Reference&							rSprite,
328                                          const uno::Reference< rendering::XPolyPolygon2D >& xClip )
329     {
330         // NULL xClip explicitely allowed here (to clear clipping)
331 
332         // retrieve bounds before and after clip change.
333         const ::basegfx::B2DRectangle& rPrevBounds( getUpdateArea() );
334 
335         mxClipPoly = xClip;
336 
337         if( !updateClipState( rSprite ) &&
338             mbActive )
339         {
340             mpSpriteCanvas->updateSprite( rSprite,
341                                           maPosition,
342                                           rPrevBounds );
343             mpSpriteCanvas->updateSprite( rSprite,
344                                           maPosition,
345                                           getUpdateArea() );
346         }
347 
348         mbClipDirty = true;
349     }
350 
351     void CanvasCustomSpriteHelper::setPriority( const Sprite::Reference&	rSprite,
352                                                 double 						nPriority )
353     {
354         if( !mpSpriteCanvas.get() )
355             return; // we're disposed
356 
357         if( nPriority != mfPriority )
358         {
359             mfPriority = nPriority;
360 
361             if( mbActive )
362             {
363                 mpSpriteCanvas->updateSprite( rSprite,
364                                               maPosition,
365                                               getUpdateArea() );
366             }
367 
368             mbPrioDirty = true;
369         }
370     }
371 
372     void CanvasCustomSpriteHelper::show( const Sprite::Reference& rSprite )
373     {
374         if( !mpSpriteCanvas.get() )
375             return; // we're disposed
376 
377         if( !mbActive )
378         {
379             mpSpriteCanvas->showSprite( rSprite );
380             mbActive = true;
381 
382             // TODO(P1): if clip is the NULL clip (nothing visible),
383             // also save us the update call.
384 
385             if( mfAlpha != 0.0 )
386             {
387                 mpSpriteCanvas->updateSprite( rSprite,
388                                               maPosition,
389                                               getUpdateArea() );
390             }
391 
392             mbVisibilityDirty = true;
393         }
394     }
395 
396     void CanvasCustomSpriteHelper::hide( const Sprite::Reference& rSprite )
397     {
398         if( !mpSpriteCanvas.get() )
399             return; // we're disposed
400 
401         if( mbActive )
402         {
403             mpSpriteCanvas->hideSprite( rSprite );
404             mbActive = false;
405 
406             // TODO(P1): if clip is the NULL clip (nothing visible),
407             // also save us the update call.
408 
409             if( mfAlpha != 0.0 )
410             {
411                 mpSpriteCanvas->updateSprite( rSprite,
412                                               maPosition,
413                                               getUpdateArea() );
414             }
415 
416             mbVisibilityDirty = true;
417         }
418     }
419 
420     // Sprite interface
421     bool CanvasCustomSpriteHelper::isAreaUpdateOpaque( const ::basegfx::B2DRange& rUpdateArea ) const
422     {
423         if( !mbIsCurrClipRectangle ||
424             !mbIsContentFullyOpaque ||
425             !::rtl::math::approxEqual(mfAlpha, 1.0) )
426         {
427             // sprite either transparent, or clip rect does not
428             // represent exact bounds -> update might not be fully
429             // opaque
430             return false;
431         }
432         else
433         {
434             // make sure sprite rect fully covers update area -
435             // although the update area originates from the sprite,
436             // it's by no means guaranteed that it's limited to this
437             // sprite's update area - after all, other sprites might
438             // have been merged, or this sprite is moving.
439             return getUpdateArea().isInside( rUpdateArea );
440         }
441     }
442 
443     ::basegfx::B2DPoint CanvasCustomSpriteHelper::getPosPixel() const
444     {
445         return maPosition;
446     }
447 
448     ::basegfx::B2DVector CanvasCustomSpriteHelper::getSizePixel() const
449     {
450         return maSize;
451     }
452 
453     ::basegfx::B2DRange CanvasCustomSpriteHelper::getUpdateArea( const ::basegfx::B2DRange& rBounds ) const
454     {
455         // Internal! Only call with locked object mutex!
456         ::basegfx::B2DHomMatrix aTransform( maTransform );
457         aTransform.translate( maPosition.getX(),
458                               maPosition.getY() );
459 
460         // transform bounds at origin, as the sprite transformation is
461         // formulated that way
462         ::basegfx::B2DRectangle aTransformedBounds;
463         return ::canvas::tools::calcTransformedRectBounds( aTransformedBounds,
464                                                            rBounds,
465                                                            aTransform );
466     }
467 
468     ::basegfx::B2DRange CanvasCustomSpriteHelper::getUpdateArea() const
469     {
470         // Internal! Only call with locked object mutex!
471 
472         // return effective sprite rect, i.e. take active clip into
473         // account
474         if( maCurrClipBounds.isEmpty() )
475             return getUpdateArea( ::basegfx::B2DRectangle( 0.0, 0.0,
476                                                            maSize.getX(),
477                                                            maSize.getY() ) );
478         else
479             return ::basegfx::B2DRectangle(
480                 maPosition + maCurrClipBounds.getMinimum(),
481                 maPosition + maCurrClipBounds.getMaximum() );
482     }
483 
484     double CanvasCustomSpriteHelper::getPriority() const
485     {
486         return mfPriority;
487     }
488 
489     ::basegfx::B2DRange CanvasCustomSpriteHelper::getFullSpriteRect() const
490     {
491         // Internal! Only call with locked object mutex!
492         return getUpdateArea( ::basegfx::B2DRectangle( 0.0, 0.0,
493                                                        maSize.getX(),
494                                                        maSize.getY() ) );
495     }
496 }
497