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