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