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 
10 #include <sal/config.h>
11 
12 #include <memory>
13 #include <functional>
14 #include <epoxy/gl.h>
15 
16 #include <basegfx/matrix/b2dhommatrix.hxx>
17 #include <basegfx/polygon/b2dpolypolygon.hxx>
18 #include <basegfx/polygon/b2dpolypolygontools.hxx>
19 #include <basegfx/utils/canvastools.hxx>
20 #include <com/sun/star/rendering/CompositeOperation.hpp>
21 #include <rtl/crc.h>
22 #include <rtl/math.hxx>
23 #include <tools/diagnose_ex.h>
24 #include <vcl/font.hxx>
25 #include <vcl/metric.hxx>
26 #include <vcl/virdev.hxx>
27 
28 #include "ogl_canvasbitmap.hxx"
29 #include "ogl_canvasfont.hxx"
30 #include "ogl_canvastools.hxx"
31 #include "ogl_texturecache.hxx"
32 #include "ogl_tools.hxx"
33 
34 #include "ogl_canvashelper.hxx"
35 
36 using namespace ::com::sun::star;
37 using namespace std::placeholders;
38 
39 namespace oglcanvas
40 {
41     /* Concepts:
42        =========
43 
44        This OpenGL canvas implementation tries to keep all render
45        output as high-level as possible, i.e. geometry data and
46        externally-provided bitmaps. Therefore, calls at the
47        XCanvas-interfaces are not immediately transformed into colored
48        pixel inside some GL buffer, but are retained simply with their
49        call parameters. Only after XSpriteCanvas::updateScreen() has
50        been called, this all gets transferred to the OpenGL subsystem
51        and converted to a visible scene. The big advantage is, this
52        makes sprite modifications practically zero-overhead, and saves
53        a lot on texture memory (compared to the directx canvas, which
54        immediately dumps every render call into a texture).
55 
56        The drawback, of course, is that complex images churn a lot of
57        GPU cycles on every re-rendering.
58 
59        For the while, I'll be using immediate mode, i.e. transfer data
60        over and over again to the OpenGL subsystem. Alternatively,
61        there are display lists, which at least keep the data on the
62        server, or even better, vertex buffers, which copy geometry
63        data over en bloc.
64 
65        Next todo: put polygon geometry into vertex buffer (LRU cache
66        necessary?) - or, rather, buffer objects! prune entries older
67        than one updateScreen() call)
68 
69        Text: http://www.opengl.org/resources/features/fontsurvey/
70      */
71 
72     struct CanvasHelper::Action
73     {
74         ::basegfx::B2DHomMatrix         maTransform;
75         GLenum                          meSrcBlendMode;
76         GLenum                          meDstBlendMode;
77         rendering::ARGBColor            maARGBColor;
78         ::basegfx::B2DPolyPolygonVector maPolyPolys;
79 
80         std::function< bool (
81                             const CanvasHelper&,
82                             const ::basegfx::B2DHomMatrix&,
83                             GLenum,
84                             GLenum,
85                             const rendering::ARGBColor&,
86                             const ::basegfx::B2DPolyPolygonVector&)> maFunction;
87     };
88 
89     namespace
90     {
lcl_drawLine(const CanvasHelper &,const::basegfx::B2DHomMatrix & rTransform,GLenum eSrcBlend,GLenum eDstBlend,const rendering::ARGBColor & rColor,const geometry::RealPoint2D & rStartPoint,const geometry::RealPoint2D & rEndPoint)91         bool lcl_drawLine( const CanvasHelper&              /*rHelper*/,
92                            const ::basegfx::B2DHomMatrix&   rTransform,
93                            GLenum                           eSrcBlend,
94                            GLenum                           eDstBlend,
95                            const rendering::ARGBColor&      rColor,
96                            const geometry::RealPoint2D&     rStartPoint,
97                            const geometry::RealPoint2D&     rEndPoint )
98         {
99             TransformationPreserver aPreserver;
100             setupState(rTransform, eSrcBlend, eDstBlend, rColor);
101 
102             glBegin(GL_LINES);
103             glVertex2d(rStartPoint.X, rStartPoint.Y);
104             glVertex2d(rEndPoint.X, rEndPoint.Y);
105             glEnd();
106 
107             return true;
108         }
109 
lcl_drawPolyPolygon(const CanvasHelper &,const::basegfx::B2DHomMatrix & rTransform,GLenum eSrcBlend,GLenum eDstBlend,const rendering::ARGBColor & rColor,const::basegfx::B2DPolyPolygonVector & rPolyPolygons)110         bool lcl_drawPolyPolygon( const CanvasHelper&                    /*rHelper*/,
111                                   const ::basegfx::B2DHomMatrix&         rTransform,
112                                   GLenum                                 eSrcBlend,
113                                   GLenum                                 eDstBlend,
114                                   const rendering::ARGBColor&            rColor,
115                                   const ::basegfx::B2DPolyPolygonVector& rPolyPolygons )
116         {
117             TransformationPreserver aPreserver;
118             setupState(rTransform, eSrcBlend, eDstBlend, rColor);
119 
120             for( const auto& rPoly : rPolyPolygons )
121                 renderPolyPolygon( rPoly );
122 
123             return true;
124         }
125 
lcl_fillPolyPolygon(const CanvasHelper &,const::basegfx::B2DHomMatrix & rTransform,GLenum eSrcBlend,GLenum eDstBlend,const rendering::ARGBColor & rColor,const::basegfx::B2DPolyPolygonVector & rPolyPolygons)126         bool lcl_fillPolyPolygon( const CanvasHelper&                    /*rHelper*/,
127                                   const ::basegfx::B2DHomMatrix&         rTransform,
128                                   GLenum                                 eSrcBlend,
129                                   GLenum                                 eDstBlend,
130                                   const rendering::ARGBColor&            rColor,
131                                   const ::basegfx::B2DPolyPolygonVector& rPolyPolygons )
132         {
133             TransformationPreserver aPreserver;
134             setupState(rTransform, eSrcBlend, eDstBlend, rColor);
135 
136             for( const auto& rPoly : rPolyPolygons )
137             {
138                 glBegin( GL_TRIANGLES );
139                 renderComplexPolyPolygon( rPoly );
140                 glEnd();
141             }
142 
143             return true;
144         }
145 
lcl_fillGradientPolyPolygon(const CanvasHelper & rHelper,const::basegfx::B2DHomMatrix & rTransform,GLenum eSrcBlend,GLenum eDstBlend,const::canvas::ParametricPolyPolygon::Values & rValues,const rendering::Texture & rTexture,const::basegfx::B2DPolyPolygonVector & rPolyPolygons)146         bool lcl_fillGradientPolyPolygon( const CanvasHelper&                            rHelper,
147                                           const ::basegfx::B2DHomMatrix&                 rTransform,
148                                           GLenum                                         eSrcBlend,
149                                           GLenum                                         eDstBlend,
150                                           const ::canvas::ParametricPolyPolygon::Values& rValues,
151                                           const rendering::Texture&                      rTexture,
152                                           const ::basegfx::B2DPolyPolygonVector&         rPolyPolygons )
153         {
154             TransformationPreserver aPreserver;
155             setupState(rTransform, eSrcBlend, eDstBlend, rendering::ARGBColor());
156 
157             // convert to weird canvas textur coordinate system (not
158             // [0,1]^2, but path coordinate system)
159             ::basegfx::B2DHomMatrix aTextureTransform;
160             ::basegfx::unotools::homMatrixFromAffineMatrix( aTextureTransform,
161                                                             rTexture.AffineTransform );
162             ::basegfx::B2DRange aBounds;
163             for( const auto& rPoly : rPolyPolygons )
164                 aBounds.expand( ::basegfx::utils::getRange( rPoly ) );
165             aTextureTransform.translate(-aBounds.getMinX(), -aBounds.getMinY());
166             aTextureTransform.scale(1/aBounds.getWidth(), 1/aBounds.getHeight());
167 
168             const sal_Int32 nNumCols=rValues.maColors.getLength();
169             uno::Sequence< rendering::ARGBColor > aColors(nNumCols);
170             rendering::ARGBColor* const pColors=aColors.getArray();
171             rendering::ARGBColor* pCurrCol=pColors;
172             for( sal_Int32 i=0; i<nNumCols; ++i )
173                 *pCurrCol++ = rHelper.getDevice()->getDeviceColorSpace()->convertToARGB(rValues.maColors[i])[0];
174 
175             OSL_ASSERT(nNumCols == rValues.maStops.getLength());
176 
177             switch( rValues.meType )
178             {
179                 case ::canvas::ParametricPolyPolygon::GradientType::Linear:
180                     rHelper.getDeviceHelper()->useLinearGradientShader(pColors,
181                                                                        rValues.maStops,
182                                                                        aTextureTransform);
183                     break;
184 
185                 case ::canvas::ParametricPolyPolygon::GradientType::Elliptical:
186                     rHelper.getDeviceHelper()->useRadialGradientShader(pColors,
187                                                                        rValues.maStops,
188                                                                        aTextureTransform);
189                     break;
190 
191                 case ::canvas::ParametricPolyPolygon::GradientType::Rectangular:
192                     rHelper.getDeviceHelper()->useRectangularGradientShader(pColors,
193                                                                             rValues.maStops,
194                                                                             aTextureTransform);
195                     break;
196 
197                 default:
198                     ENSURE_OR_THROW( false,
199                                       "CanvasHelper lcl_fillGradientPolyPolygon(): Unexpected case" );
200             }
201 
202 
203             for( const auto& rPoly : rPolyPolygons )
204             {
205                 glBegin(GL_TRIANGLES);
206                 renderComplexPolyPolygon( rPoly );
207                 glEnd();
208             }
209 
210             glUseProgram(0);
211             glLoadIdentity();
212             glMatrixMode(GL_MODELVIEW);
213 
214             return true;
215         }
216 
lcl_drawOwnBitmap(const CanvasHelper &,const::basegfx::B2DHomMatrix & rTransform,GLenum eSrcBlend,GLenum eDstBlend,const rendering::ARGBColor & rColor,const CanvasBitmap & rBitmap)217         bool lcl_drawOwnBitmap( const CanvasHelper&              /*rHelper*/,
218                                 const ::basegfx::B2DHomMatrix&   rTransform,
219                                 GLenum                           eSrcBlend,
220                                 GLenum                           eDstBlend,
221                                 const rendering::ARGBColor&      rColor,
222                                 const CanvasBitmap&              rBitmap )
223         {
224             TransformationPreserver aPreserver;
225             setupState(rTransform, eSrcBlend, eDstBlend, rColor);
226 
227             return rBitmap.renderRecordedActions();
228         }
229 
lcl_drawGenericBitmap(const CanvasHelper & rHelper,const::basegfx::B2DHomMatrix & rTransform,GLenum eSrcBlend,GLenum eDstBlend,const rendering::ARGBColor & rColor,const geometry::IntegerSize2D & rPixelSize,const uno::Sequence<sal_Int8> & rPixelData,sal_uInt32 nPixelCrc32)230         bool lcl_drawGenericBitmap( const CanvasHelper&              rHelper,
231                                     const ::basegfx::B2DHomMatrix&   rTransform,
232                                     GLenum                           eSrcBlend,
233                                     GLenum                           eDstBlend,
234                                     const rendering::ARGBColor&      rColor,
235                                     const geometry::IntegerSize2D&   rPixelSize,
236                                     const uno::Sequence<sal_Int8>&   rPixelData,
237                                     sal_uInt32                       nPixelCrc32 )
238         {
239             TransformationPreserver aPreserver;
240             setupState(rTransform, eSrcBlend, eDstBlend, rColor);
241 
242             const unsigned int nTexId=rHelper.getDeviceHelper()->getTextureCache().getTexture(
243                 rPixelSize, rPixelData.getConstArray(), nPixelCrc32);
244 
245             glBindTexture(GL_TEXTURE_2D, nTexId);
246             glEnable(GL_TEXTURE_2D);
247             glTexParameteri(GL_TEXTURE_2D,
248                             GL_TEXTURE_MIN_FILTER,
249                             GL_NEAREST);
250             glTexParameteri(GL_TEXTURE_2D,
251                             GL_TEXTURE_MAG_FILTER,
252                             GL_NEAREST);
253             glEnable(GL_BLEND);
254             glBlendFunc(GL_SRC_ALPHA,
255                         GL_ONE_MINUS_SRC_ALPHA);
256 
257             // blend against fixed vertex color; texture alpha is multiplied in
258             glColor4f(1,1,1,1);
259 
260             glBegin(GL_TRIANGLE_STRIP);
261             glTexCoord2f(0,0); glVertex2d(0,0);
262             glTexCoord2f(0,1); glVertex2d(0, rPixelSize.Height);
263             glTexCoord2f(1,0); glVertex2d(rPixelSize.Width,0);
264             glTexCoord2f(1,1); glVertex2d(rPixelSize.Width,rPixelSize.Height);
265             glEnd();
266 
267             glBindTexture(GL_TEXTURE_2D, 0);
268             glDisable(GL_TEXTURE_2D);
269 
270             return true;
271         }
272 
lcl_fillTexturedPolyPolygon(const CanvasHelper & rHelper,const::basegfx::B2DHomMatrix & rTransform,GLenum eSrcBlend,GLenum eDstBlend,const rendering::Texture & rTexture,const geometry::IntegerSize2D & rPixelSize,const uno::Sequence<sal_Int8> & rPixelData,sal_uInt32 nPixelCrc32,const::basegfx::B2DPolyPolygonVector & rPolyPolygons)273         bool lcl_fillTexturedPolyPolygon( const CanvasHelper&                    rHelper,
274                                           const ::basegfx::B2DHomMatrix&         rTransform,
275                                           GLenum                                 eSrcBlend,
276                                           GLenum                                 eDstBlend,
277                                           const rendering::Texture&              rTexture,
278                                           const geometry::IntegerSize2D&         rPixelSize,
279                                           const uno::Sequence<sal_Int8>&         rPixelData,
280                                           sal_uInt32                             nPixelCrc32,
281                                           const ::basegfx::B2DPolyPolygonVector& rPolyPolygons )
282         {
283             TransformationPreserver aPreserver;
284             setupState(rTransform, eSrcBlend, eDstBlend, rendering::ARGBColor());
285 
286             const unsigned int nTexId=rHelper.getDeviceHelper()->getTextureCache().getTexture(
287                 rPixelSize, rPixelData.getConstArray(), nPixelCrc32);
288 
289             glBindTexture(GL_TEXTURE_2D, nTexId);
290             glEnable(GL_TEXTURE_2D);
291             glTexParameteri(GL_TEXTURE_2D,
292                             GL_TEXTURE_MIN_FILTER,
293                             GL_NEAREST);
294             glTexParameteri(GL_TEXTURE_2D,
295                             GL_TEXTURE_MAG_FILTER,
296                             GL_NEAREST);
297             glEnable(GL_BLEND);
298             glBlendFunc(GL_SRC_ALPHA,
299                         GL_ONE_MINUS_SRC_ALPHA);
300 
301             // convert to weird canvas textur coordinate system (not
302             // [0,1]^2, but path coordinate system)
303             ::basegfx::B2DHomMatrix aTextureTransform;
304             ::basegfx::unotools::homMatrixFromAffineMatrix( aTextureTransform,
305                                                             rTexture.AffineTransform );
306             ::basegfx::B2DRange aBounds;
307             for( const auto& rPolyPolygon : rPolyPolygons )
308                 aBounds.expand( ::basegfx::utils::getRange( rPolyPolygon ) );
309             aTextureTransform.translate(-aBounds.getMinX(), -aBounds.getMinY());
310             aTextureTransform.scale(1/aBounds.getWidth(), 1/aBounds.getHeight());
311             aTextureTransform.invert();
312 
313             glMatrixMode(GL_TEXTURE);
314             double aTexTransform[] =
315                 {
316                     aTextureTransform.get(0,0), aTextureTransform.get(1,0), 0, 0,
317                     aTextureTransform.get(0,1), aTextureTransform.get(1,1), 0, 0,
318                     0,                          0,                          1, 0,
319                     aTextureTransform.get(0,2), aTextureTransform.get(1,2), 0, 1
320                 };
321             glLoadMatrixd(aTexTransform);
322 
323             // blend against fixed vertex color; texture alpha is multiplied in
324             glColor4f(1,1,1,rTexture.Alpha);
325 
326             for( const auto& rPolyPolygon : rPolyPolygons )
327             {
328                 glBegin(GL_TRIANGLES);
329                 renderComplexPolyPolygon( rPolyPolygon );
330                 glEnd();
331             }
332 
333             glLoadIdentity();
334             glMatrixMode(GL_MODELVIEW);
335 
336             glBindTexture(GL_TEXTURE_2D, 0);
337             glDisable(GL_TEXTURE_2D);
338 
339             return true;
340         }
341     }
342 
CanvasHelper()343     CanvasHelper::CanvasHelper() :
344         mpDevice( nullptr ),
345         mpDeviceHelper( nullptr ),
346         mpRecordedActions()
347     {}
348 
~CanvasHelper()349     CanvasHelper::~CanvasHelper()
350     {}
351 
operator =(const CanvasHelper & rSrc)352     CanvasHelper& CanvasHelper::operator=( const CanvasHelper& rSrc )
353     {
354         mpDevice = rSrc.mpDevice;
355         mpDeviceHelper = rSrc.mpDeviceHelper;
356         mpRecordedActions = rSrc.mpRecordedActions;
357         return *this;
358     }
359 
disposing()360     void CanvasHelper::disposing()
361     {
362         RecordVectorT aThrowaway;
363         mpRecordedActions.swap( aThrowaway );
364         mpDevice = nullptr;
365         mpDeviceHelper = nullptr;
366     }
367 
init(rendering::XGraphicDevice & rDevice,SpriteDeviceHelper & rDeviceHelper)368     void CanvasHelper::init( rendering::XGraphicDevice& rDevice,
369                              SpriteDeviceHelper& rDeviceHelper )
370     {
371         mpDevice = &rDevice;
372         mpDeviceHelper = &rDeviceHelper;
373     }
374 
clear()375     void CanvasHelper::clear()
376     {
377         mpRecordedActions->clear();
378     }
379 
drawLine(const rendering::XCanvas *,const geometry::RealPoint2D & aStartPoint,const geometry::RealPoint2D & aEndPoint,const rendering::ViewState & viewState,const rendering::RenderState & renderState)380     void CanvasHelper::drawLine( const rendering::XCanvas*      /*pCanvas*/,
381                                  const geometry::RealPoint2D&   aStartPoint,
382                                  const geometry::RealPoint2D&   aEndPoint,
383                                  const rendering::ViewState&    viewState,
384                                  const rendering::RenderState&  renderState )
385     {
386         if( mpDevice )
387         {
388             mpRecordedActions->push_back( Action() );
389             Action& rAct=mpRecordedActions->back();
390 
391             setupGraphicsState( rAct, viewState, renderState );
392             rAct.maFunction = std::bind(&lcl_drawLine,
393                                           std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5,
394                                           aStartPoint, aEndPoint);
395         }
396     }
397 
drawBezier(const rendering::XCanvas *,const geometry::RealBezierSegment2D & aBezierSegment,const geometry::RealPoint2D & aEndPoint,const rendering::ViewState & viewState,const rendering::RenderState & renderState)398     void CanvasHelper::drawBezier( const rendering::XCanvas*            /*pCanvas*/,
399                                    const geometry::RealBezierSegment2D& aBezierSegment,
400                                    const geometry::RealPoint2D&         aEndPoint,
401                                    const rendering::ViewState&          viewState,
402                                    const rendering::RenderState&        renderState )
403     {
404         if( !mpDevice )
405             return;
406 
407         mpRecordedActions->push_back( Action() );
408         Action& rAct=mpRecordedActions->back();
409 
410         setupGraphicsState( rAct, viewState, renderState );
411 
412         // TODO(F2): subdivide&render whole curve
413         rAct.maFunction = std::bind(&lcl_drawLine,
414                                         std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5,
415                                         geometry::RealPoint2D(
416                                             aBezierSegment.Px,
417                                             aBezierSegment.Py),
418                                         aEndPoint);
419     }
420 
drawPolyPolygon(const rendering::XCanvas *,const uno::Reference<rendering::XPolyPolygon2D> & xPolyPolygon,const rendering::ViewState & viewState,const rendering::RenderState & renderState)421     uno::Reference< rendering::XCachedPrimitive > CanvasHelper::drawPolyPolygon( const rendering::XCanvas*                          /*pCanvas*/,
422                                                                                  const uno::Reference< rendering::XPolyPolygon2D >& xPolyPolygon,
423                                                                                  const rendering::ViewState&                        viewState,
424                                                                                  const rendering::RenderState&                      renderState )
425     {
426         ENSURE_OR_THROW( xPolyPolygon.is(),
427                           "CanvasHelper::drawPolyPolygon: polygon is NULL");
428 
429         if( mpDevice )
430         {
431             mpRecordedActions->push_back( Action() );
432             Action& rAct=mpRecordedActions->back();
433 
434             setupGraphicsState( rAct, viewState, renderState );
435             rAct.maPolyPolys.push_back(
436                 ::basegfx::unotools::b2DPolyPolygonFromXPolyPolygon2D(xPolyPolygon));
437             rAct.maPolyPolys.back().makeUnique(); // own copy, for thread safety
438 
439             rAct.maFunction = &lcl_drawPolyPolygon;
440         }
441 
442         // TODO(P1): Provide caching here.
443         return uno::Reference< rendering::XCachedPrimitive >(nullptr);
444     }
445 
strokePolyPolygon(const rendering::XCanvas *,const uno::Reference<rendering::XPolyPolygon2D> & xPolyPolygon,const rendering::ViewState & viewState,const rendering::RenderState & renderState,const rendering::StrokeAttributes &)446     uno::Reference< rendering::XCachedPrimitive > CanvasHelper::strokePolyPolygon( const rendering::XCanvas*                            /*pCanvas*/,
447                                                                                    const uno::Reference< rendering::XPolyPolygon2D >&   xPolyPolygon,
448                                                                                    const rendering::ViewState&                          viewState,
449                                                                                    const rendering::RenderState&                        renderState,
450                                                                                    const rendering::StrokeAttributes&                   /*strokeAttributes*/ )
451     {
452         ENSURE_OR_THROW( xPolyPolygon.is(),
453                           "CanvasHelper::strokePolyPolygon: polygon is NULL");
454 
455         if( mpDevice )
456         {
457             mpRecordedActions->push_back( Action() );
458             Action& rAct=mpRecordedActions->back();
459 
460             setupGraphicsState( rAct, viewState, renderState );
461             rAct.maPolyPolys.push_back(
462                 ::basegfx::unotools::b2DPolyPolygonFromXPolyPolygon2D(xPolyPolygon));
463             rAct.maPolyPolys.back().makeUnique(); // own copy, for thread safety
464 
465             // TODO(F3): fallback to drawPolyPolygon currently
466             rAct.maFunction = &lcl_drawPolyPolygon;
467         }
468 
469         // TODO(P1): Provide caching here.
470         return uno::Reference< rendering::XCachedPrimitive >(nullptr);
471     }
472 
strokeTexturedPolyPolygon(const rendering::XCanvas *,const uno::Reference<rendering::XPolyPolygon2D> &,const rendering::ViewState &,const rendering::RenderState &,const uno::Sequence<rendering::Texture> &,const rendering::StrokeAttributes &)473     uno::Reference< rendering::XCachedPrimitive > CanvasHelper::strokeTexturedPolyPolygon( const rendering::XCanvas*                            /*pCanvas*/,
474                                                                                            const uno::Reference< rendering::XPolyPolygon2D >&   /*xPolyPolygon*/,
475                                                                                            const rendering::ViewState&                          /*viewState*/,
476                                                                                            const rendering::RenderState&                        /*renderState*/,
477                                                                                            const uno::Sequence< rendering::Texture >&           /*textures*/,
478                                                                                            const rendering::StrokeAttributes&                   /*strokeAttributes*/ )
479     {
480         // TODO
481         return uno::Reference< rendering::XCachedPrimitive >(nullptr);
482     }
483 
strokeTextureMappedPolyPolygon(const rendering::XCanvas *,const uno::Reference<rendering::XPolyPolygon2D> &,const rendering::ViewState &,const rendering::RenderState &,const uno::Sequence<rendering::Texture> &,const uno::Reference<geometry::XMapping2D> &,const rendering::StrokeAttributes &)484     uno::Reference< rendering::XCachedPrimitive > CanvasHelper::strokeTextureMappedPolyPolygon( const rendering::XCanvas*                           /*pCanvas*/,
485                                                                                                 const uno::Reference< rendering::XPolyPolygon2D >&  /*xPolyPolygon*/,
486                                                                                                 const rendering::ViewState&                         /*viewState*/,
487                                                                                                 const rendering::RenderState&                       /*renderState*/,
488                                                                                                 const uno::Sequence< rendering::Texture >&          /*textures*/,
489                                                                                                 const uno::Reference< geometry::XMapping2D >&       /*xMapping*/,
490                                                                                                 const rendering::StrokeAttributes&                  /*strokeAttributes*/ )
491     {
492         // TODO
493         return uno::Reference< rendering::XCachedPrimitive >(nullptr);
494     }
495 
queryStrokeShapes(const rendering::XCanvas *,const uno::Reference<rendering::XPolyPolygon2D> &,const rendering::ViewState &,const rendering::RenderState &,const rendering::StrokeAttributes &)496     uno::Reference< rendering::XPolyPolygon2D >   CanvasHelper::queryStrokeShapes( const rendering::XCanvas*                            /*pCanvas*/,
497                                                                                    const uno::Reference< rendering::XPolyPolygon2D >&   /*xPolyPolygon*/,
498                                                                                    const rendering::ViewState&                          /*viewState*/,
499                                                                                    const rendering::RenderState&                        /*renderState*/,
500                                                                                    const rendering::StrokeAttributes&                   /*strokeAttributes*/ )
501     {
502         // TODO
503         return uno::Reference< rendering::XPolyPolygon2D >(nullptr);
504     }
505 
fillPolyPolygon(const rendering::XCanvas *,const uno::Reference<rendering::XPolyPolygon2D> & xPolyPolygon,const rendering::ViewState & viewState,const rendering::RenderState & renderState)506     uno::Reference< rendering::XCachedPrimitive > CanvasHelper::fillPolyPolygon( const rendering::XCanvas*                          /*pCanvas*/,
507                                                                                  const uno::Reference< rendering::XPolyPolygon2D >& xPolyPolygon,
508                                                                                  const rendering::ViewState&                        viewState,
509                                                                                  const rendering::RenderState&                      renderState )
510     {
511         ENSURE_OR_THROW( xPolyPolygon.is(),
512                           "CanvasHelper::fillPolyPolygon: polygon is NULL");
513 
514         if( mpDevice )
515         {
516             mpRecordedActions->push_back( Action() );
517             Action& rAct=mpRecordedActions->back();
518 
519             setupGraphicsState( rAct, viewState, renderState );
520             rAct.maPolyPolys.push_back(
521                 ::basegfx::unotools::b2DPolyPolygonFromXPolyPolygon2D(xPolyPolygon));
522             rAct.maPolyPolys.back().makeUnique(); // own copy, for thread safety
523 
524             rAct.maFunction = &lcl_fillPolyPolygon;
525         }
526 
527         // TODO(P1): Provide caching here.
528         return uno::Reference< rendering::XCachedPrimitive >(nullptr);
529     }
530 
fillTexturedPolyPolygon(const rendering::XCanvas *,const uno::Reference<rendering::XPolyPolygon2D> & xPolyPolygon,const rendering::ViewState & viewState,const rendering::RenderState & renderState,const uno::Sequence<rendering::Texture> & textures)531     uno::Reference< rendering::XCachedPrimitive > CanvasHelper::fillTexturedPolyPolygon( const rendering::XCanvas*                          /*pCanvas*/,
532                                                                                          const uno::Reference< rendering::XPolyPolygon2D >& xPolyPolygon,
533                                                                                          const rendering::ViewState&                        viewState,
534                                                                                          const rendering::RenderState&                      renderState,
535                                                                                          const uno::Sequence< rendering::Texture >&         textures )
536     {
537         ENSURE_OR_THROW( xPolyPolygon.is(),
538                           "CanvasHelper::fillPolyPolygon: polygon is NULL");
539 
540         if( mpDevice )
541         {
542             mpRecordedActions->push_back( Action() );
543             Action& rAct=mpRecordedActions->back();
544 
545             setupGraphicsState( rAct, viewState, renderState );
546             rAct.maPolyPolys.push_back(
547                 ::basegfx::unotools::b2DPolyPolygonFromXPolyPolygon2D(xPolyPolygon));
548             rAct.maPolyPolys.back().makeUnique(); // own copy, for thread safety
549 
550             // TODO(F1): Multi-texturing
551             if( textures[0].Gradient.is() )
552             {
553                 // try to cast XParametricPolyPolygon2D reference to
554                 // our implementation class.
555                 ::canvas::ParametricPolyPolygon* pGradient =
556                       dynamic_cast< ::canvas::ParametricPolyPolygon* >( textures[0].Gradient.get() );
557 
558                 if( pGradient )
559                 {
560                     // copy state from Gradient polypoly locally
561                     // (given object might change!)
562                     const ::canvas::ParametricPolyPolygon::Values& rValues(
563                         pGradient->getValues() );
564 
565                     rAct.maFunction = std::bind(&lcl_fillGradientPolyPolygon,
566                                                     std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4,
567                                                     rValues,
568                                                     textures[0],
569                                                     std::placeholders::_6);
570                 }
571                 else
572                 {
573                     // TODO(F1): The generic case is missing here
574                     ENSURE_OR_THROW( false,
575                                       "CanvasHelper::fillTexturedPolyPolygon(): unknown parametric polygon encountered" );
576                 }
577             }
578             else if( textures[0].Bitmap.is() )
579             {
580                 // own bitmap?
581                 CanvasBitmap* pOwnBitmap=dynamic_cast<CanvasBitmap*>(textures[0].Bitmap.get());
582                 if( pOwnBitmap )
583                 {
584                     // TODO(F2): own texture bitmap
585                 }
586                 else
587                 {
588                     // TODO(P3): Highly inefficient - simply copies pixel data
589 
590                     uno::Reference< rendering::XIntegerReadOnlyBitmap > xIntegerBitmap(
591                         textures[0].Bitmap,
592                         uno::UNO_QUERY);
593                     if( xIntegerBitmap.is() )
594                     {
595                         const geometry::IntegerSize2D aSize=xIntegerBitmap->getSize();
596                         rendering::IntegerBitmapLayout aLayout;
597                         uno::Sequence<sal_Int8> aPixelData=
598                             xIntegerBitmap->getData(
599                                 aLayout,
600                                 geometry::IntegerRectangle2D(0,0,aSize.Width,aSize.Height));
601 
602                         // force-convert color to ARGB8888 int color space
603                         uno::Sequence<sal_Int8> aARGBBytes(
604                             aLayout.ColorSpace->convertToIntegerColorSpace(
605                                 aPixelData,
606                                 canvas::tools::getStdColorSpace()));
607 
608                         rAct.maFunction = std::bind(&lcl_fillTexturedPolyPolygon,
609                                                         std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4,
610                                                         textures[0],
611                                                         aSize,
612                                                         aARGBBytes,
613                                                         rtl_crc32(0,
614                                                                   aARGBBytes.getConstArray(),
615                                                                   aARGBBytes.getLength()),
616                                                         std::placeholders::_6);
617                     }
618                     // TODO(F1): handle non-integer case
619                 }
620             }
621         }
622 
623         // TODO(P1): Provide caching here.
624         return uno::Reference< rendering::XCachedPrimitive >(nullptr);
625     }
626 
fillTextureMappedPolyPolygon(const rendering::XCanvas *,const uno::Reference<rendering::XPolyPolygon2D> &,const rendering::ViewState &,const rendering::RenderState &,const uno::Sequence<rendering::Texture> &,const uno::Reference<geometry::XMapping2D> &)627     uno::Reference< rendering::XCachedPrimitive > CanvasHelper::fillTextureMappedPolyPolygon( const rendering::XCanvas*                             /*pCanvas*/,
628                                                                                               const uno::Reference< rendering::XPolyPolygon2D >&    /*xPolyPolygon*/,
629                                                                                               const rendering::ViewState&                           /*viewState*/,
630                                                                                               const rendering::RenderState&                         /*renderState*/,
631                                                                                               const uno::Sequence< rendering::Texture >&            /*textures*/,
632                                                                                               const uno::Reference< geometry::XMapping2D >&         /*xMapping*/ )
633     {
634         // TODO
635         return uno::Reference< rendering::XCachedPrimitive >(nullptr);
636     }
637 
createFont(const rendering::XCanvas *,const rendering::FontRequest & fontRequest,const uno::Sequence<beans::PropertyValue> & extraFontProperties,const geometry::Matrix2D & fontMatrix)638     uno::Reference< rendering::XCanvasFont > CanvasHelper::createFont( const rendering::XCanvas*                    /*pCanvas*/,
639                                                                        const rendering::FontRequest&                fontRequest,
640                                                                        const uno::Sequence< beans::PropertyValue >& extraFontProperties,
641                                                                        const geometry::Matrix2D&                    fontMatrix )
642     {
643         if( mpDevice )
644             return uno::Reference< rendering::XCanvasFont >(
645                     new CanvasFont(fontRequest, extraFontProperties, fontMatrix ) );
646 
647         return uno::Reference< rendering::XCanvasFont >();
648     }
649 
queryAvailableFonts(const rendering::XCanvas *,const rendering::FontInfo &,const uno::Sequence<beans::PropertyValue> &)650     uno::Sequence< rendering::FontInfo > CanvasHelper::queryAvailableFonts( const rendering::XCanvas*                       /*pCanvas*/,
651                                                                             const rendering::FontInfo&                      /*aFilter*/,
652                                                                             const uno::Sequence< beans::PropertyValue >&    /*aFontProperties*/ )
653     {
654         // TODO
655         return uno::Sequence< rendering::FontInfo >();
656     }
657 
drawText(const rendering::XCanvas *,const rendering::StringContext &,const uno::Reference<rendering::XCanvasFont> &,const rendering::ViewState &,const rendering::RenderState &,sal_Int8)658     uno::Reference< rendering::XCachedPrimitive > CanvasHelper::drawText( const rendering::XCanvas*                         /*pCanvas*/,
659                                                                           const rendering::StringContext&                   /*text*/,
660                                                                           const uno::Reference< rendering::XCanvasFont >&   /*xFont*/,
661                                                                           const rendering::ViewState&                       /*viewState*/,
662                                                                           const rendering::RenderState&                     /*renderState*/,
663                                                                           sal_Int8                                          /*textDirection*/ )
664     {
665         // TODO - but not used from slideshow
666         return uno::Reference< rendering::XCachedPrimitive >(nullptr);
667     }
668 
drawTextLayout(const rendering::XCanvas *,const uno::Reference<rendering::XTextLayout> & xLayoutetText,const rendering::ViewState & viewState,const rendering::RenderState & renderState)669     uno::Reference< rendering::XCachedPrimitive > CanvasHelper::drawTextLayout( const rendering::XCanvas*                       /*pCanvas*/,
670                                                                                 const uno::Reference< rendering::XTextLayout >& xLayoutetText,
671                                                                                 const rendering::ViewState&                     viewState,
672                                                                                 const rendering::RenderState&                   renderState )
673     {
674         ENSURE_OR_THROW( xLayoutetText.is(),
675                           "CanvasHelper::drawTextLayout: text is NULL");
676 
677         if( mpDevice )
678         {
679             ScopedVclPtrInstance< VirtualDevice > pVDev;
680             pVDev->EnableOutput(false);
681 
682             auto pLayoutFont = xLayoutetText->getFont();
683             CanvasFont* pFont=dynamic_cast<CanvasFont*>(pLayoutFont.get());
684             const rendering::StringContext& rTxt=xLayoutetText->getText();
685             if( pFont && rTxt.Length )
686             {
687                 // create the font
688                 const rendering::FontRequest& rFontRequest = pFont->getFontRequest();
689                 const geometry::Matrix2D&     rFontMatrix = pFont->getFontMatrix();
690                 vcl::Font aFont(
691                     rFontRequest.FontDescription.FamilyName,
692                     rFontRequest.FontDescription.StyleName,
693                     Size( 0, ::basegfx::fround(rFontRequest.CellSize)));
694 
695                 aFont.SetAlignment( ALIGN_BASELINE );
696                 aFont.SetCharSet( (rFontRequest.FontDescription.IsSymbolFont==util::TriState_YES) ? RTL_TEXTENCODING_SYMBOL : RTL_TEXTENCODING_UNICODE );
697                 aFont.SetVertical( rFontRequest.FontDescription.IsVertical==util::TriState_YES );
698                 aFont.SetWeight( static_cast<FontWeight>(rFontRequest.FontDescription.FontDescription.Weight) );
699                 aFont.SetItalic( (rFontRequest.FontDescription.FontDescription.Letterform<=8) ? ITALIC_NONE : ITALIC_NORMAL );
700 
701                 if (pFont->getEmphasisMark())
702                     aFont.SetEmphasisMark(FontEmphasisMark(pFont->getEmphasisMark()));
703 
704                 // adjust to stretched font
705                 if(!::rtl::math::approxEqual(rFontMatrix.m00, rFontMatrix.m11))
706                 {
707                     const Size aSize = pVDev->GetFontMetric( aFont ).GetFontSize();
708                     const double fDividend( rFontMatrix.m10 + rFontMatrix.m11 );
709                     double fStretch = rFontMatrix.m00 + rFontMatrix.m01;
710 
711                     if( !::basegfx::fTools::equalZero( fDividend) )
712                         fStretch /= fDividend;
713 
714                     const sal_Int32 nNewWidth = ::basegfx::fround( aSize.Width() * fStretch );
715 
716                     aFont.SetAverageFontWidth( nNewWidth );
717                 }
718 
719                 // set font
720                 pVDev->SetFont(aFont);
721 
722                 mpRecordedActions->push_back( Action() );
723                 Action& rAct=mpRecordedActions->back();
724 
725                 setupGraphicsState( rAct, viewState, renderState );
726 
727                 // handle custom spacing, if there
728                 uno::Sequence<double> aLogicalAdvancements=xLayoutetText->queryLogicalAdvancements();
729                 if( aLogicalAdvancements.hasElements() )
730                 {
731                     // create the DXArray
732                     const sal_Int32 nLen( aLogicalAdvancements.getLength() );
733                     std::unique_ptr<tools::Long[]> pDXArray( new tools::Long[nLen] );
734                     for( sal_Int32 i=0; i<nLen; ++i )
735                         pDXArray[i] = basegfx::fround( aLogicalAdvancements[i] );
736 
737                     // get the glyphs
738                     pVDev->GetTextOutlines(rAct.maPolyPolys,
739                                           rTxt.Text,
740                                           0,
741                                           rTxt.StartPosition,
742                                           rTxt.Length,
743                                           0,
744                                           pDXArray.get() );
745                 }
746                 else
747                 {
748                     // get the glyphs
749                     pVDev->GetTextOutlines(rAct.maPolyPolys,
750                                           rTxt.Text,
751                                           0,
752                                           rTxt.StartPosition,
753                                           rTxt.Length );
754                 }
755 
756                 // own copy, for thread safety
757                 for( auto& rPoly : rAct.maPolyPolys )
758                     rPoly.makeUnique();
759 
760                 rAct.maFunction = &lcl_fillPolyPolygon;
761             }
762         }
763 
764         // TODO
765         return uno::Reference< rendering::XCachedPrimitive >(nullptr);
766     }
767 
drawBitmap(const rendering::XCanvas *,const uno::Reference<rendering::XBitmap> & xBitmap,const rendering::ViewState & viewState,const rendering::RenderState & renderState)768     uno::Reference< rendering::XCachedPrimitive > CanvasHelper::drawBitmap( const rendering::XCanvas*                   /*pCanvas*/,
769                                                                             const uno::Reference< rendering::XBitmap >& xBitmap,
770                                                                             const rendering::ViewState&                 viewState,
771                                                                             const rendering::RenderState&               renderState )
772     {
773         ENSURE_OR_THROW( xBitmap.is(),
774                           "CanvasHelper::drawBitmap: bitmap is NULL");
775 
776         if( mpDevice )
777         {
778             // own bitmap?
779             CanvasBitmap* pOwnBitmap=dynamic_cast<CanvasBitmap*>(xBitmap.get());
780             if( pOwnBitmap )
781             {
782                 // insert as transformed copy of bitmap action vector -
783                 // during rendering, this gets rendered into a temporary
784                 // buffer, and then composited to the front
785                 mpRecordedActions->push_back( Action() );
786                 Action& rAct=mpRecordedActions->back();
787 
788                 setupGraphicsState( rAct, viewState, renderState );
789                 rAct.maFunction = std::bind(&lcl_drawOwnBitmap,
790                                                 std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5,
791                                                 *pOwnBitmap);
792             }
793             else
794             {
795                 // TODO(P3): Highly inefficient - simply copies pixel data
796 
797                 uno::Reference< rendering::XIntegerReadOnlyBitmap > xIntegerBitmap(
798                     xBitmap, uno::UNO_QUERY);
799                 if( xIntegerBitmap.is() )
800                 {
801                     const geometry::IntegerSize2D aSize=xBitmap->getSize();
802                     rendering::IntegerBitmapLayout aLayout;
803                     uno::Sequence<sal_Int8> aPixelData=
804                         xIntegerBitmap->getData(
805                             aLayout,
806                             geometry::IntegerRectangle2D(0,0,aSize.Width,aSize.Height));
807 
808                     // force-convert color to ARGB8888 int color space
809                     uno::Sequence<sal_Int8> aARGBBytes(
810                         aLayout.ColorSpace->convertToIntegerColorSpace(
811                             aPixelData,
812                             canvas::tools::getStdColorSpace()));
813 
814                     mpRecordedActions->push_back( Action() );
815                     Action& rAct=mpRecordedActions->back();
816 
817                     setupGraphicsState( rAct, viewState, renderState );
818                     rAct.maFunction = std::bind(&lcl_drawGenericBitmap,
819                                                     std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5,
820                                                     aSize, aARGBBytes,
821                                                     rtl_crc32(0,
822                                                               aARGBBytes.getConstArray(),
823                                                               aARGBBytes.getLength()));
824                 }
825                 // TODO(F1): handle non-integer case
826             }
827         }
828 
829         // TODO(P1): Provide caching here.
830         return uno::Reference< rendering::XCachedPrimitive >(nullptr);
831     }
832 
drawBitmapModulated(const rendering::XCanvas * pCanvas,const uno::Reference<rendering::XBitmap> & xBitmap,const rendering::ViewState & viewState,const rendering::RenderState & renderState)833     uno::Reference< rendering::XCachedPrimitive > CanvasHelper::drawBitmapModulated( const rendering::XCanvas*                      pCanvas,
834                                                                                      const uno::Reference< rendering::XBitmap >&    xBitmap,
835                                                                                      const rendering::ViewState&                    viewState,
836                                                                                      const rendering::RenderState&                  renderState )
837     {
838         // TODO(F3): remove this wart altogether
839         return drawBitmap(pCanvas, xBitmap, viewState, renderState);
840     }
841 
842 
setupGraphicsState(Action & o_action,const rendering::ViewState & viewState,const rendering::RenderState & renderState)843     void CanvasHelper::setupGraphicsState( Action&                       o_action,
844                                            const rendering::ViewState&   viewState,
845                                            const rendering::RenderState& renderState )
846     {
847         ENSURE_OR_THROW( mpDevice,
848                           "CanvasHelper::setupGraphicsState: reference device invalid" );
849 
850         // TODO(F3): clipping
851         // TODO(P2): think about caching transformations between canvas calls
852 
853         // setup overall transform only now. View clip above was
854         // relative to view transform
855         ::canvas::tools::mergeViewAndRenderTransform(o_action.maTransform,
856                                                      viewState,
857                                                      renderState);
858         // setup compositing - mapping courtesy David Reveman
859         // (glitz_operator.c)
860         switch( renderState.CompositeOperation )
861         {
862             case rendering::CompositeOperation::OVER:
863                 o_action.meSrcBlendMode=GL_ONE;
864                 o_action.meDstBlendMode=GL_ONE_MINUS_SRC_ALPHA;
865                 break;
866             case rendering::CompositeOperation::CLEAR:
867                 o_action.meSrcBlendMode=GL_ZERO;
868                 o_action.meDstBlendMode=GL_ZERO;
869                 break;
870             case rendering::CompositeOperation::SOURCE:
871                 o_action.meSrcBlendMode=GL_ONE;
872                 o_action.meDstBlendMode=GL_ZERO;
873                 break;
874             case rendering::CompositeOperation::UNDER:
875             case rendering::CompositeOperation::DESTINATION:
876                 o_action.meSrcBlendMode=GL_ZERO;
877                 o_action.meDstBlendMode=GL_ONE;
878                 break;
879             case rendering::CompositeOperation::INSIDE:
880                 o_action.meSrcBlendMode=GL_DST_ALPHA;
881                 o_action.meDstBlendMode=GL_ZERO;
882                 break;
883             case rendering::CompositeOperation::INSIDE_REVERSE:
884                 o_action.meSrcBlendMode=GL_ONE_MINUS_DST_ALPHA;
885                 o_action.meDstBlendMode=GL_ZERO;
886                 break;
887             case rendering::CompositeOperation::OUTSIDE:
888                 o_action.meSrcBlendMode=GL_ONE_MINUS_DST_ALPHA;
889                 o_action.meDstBlendMode=GL_ONE;
890                 break;
891             case rendering::CompositeOperation::OUTSIDE_REVERSE:
892                 o_action.meSrcBlendMode=GL_ZERO;
893                 o_action.meDstBlendMode=GL_ONE_MINUS_SRC_ALPHA;
894                 break;
895             case rendering::CompositeOperation::ATOP:
896                 o_action.meSrcBlendMode=GL_DST_ALPHA;
897                 o_action.meDstBlendMode=GL_ONE_MINUS_SRC_ALPHA;
898                 break;
899             case rendering::CompositeOperation::ATOP_REVERSE:
900                 o_action.meSrcBlendMode=GL_ONE_MINUS_DST_ALPHA;
901                 o_action.meDstBlendMode=GL_SRC_ALPHA;
902                 break;
903             case rendering::CompositeOperation::XOR:
904                 o_action.meSrcBlendMode=GL_ONE_MINUS_DST_ALPHA;
905                 o_action.meDstBlendMode=GL_ONE_MINUS_SRC_ALPHA;
906                 break;
907             case rendering::CompositeOperation::ADD:
908                 o_action.meSrcBlendMode=GL_ONE;
909                 o_action.meDstBlendMode=GL_ONE;
910                 break;
911             case rendering::CompositeOperation::SATURATE:
912                 o_action.meSrcBlendMode=GL_SRC_ALPHA_SATURATE;
913                 o_action.meDstBlendMode=GL_SRC_ALPHA_SATURATE;
914                 break;
915 
916             default:
917                 ENSURE_OR_THROW( false, "CanvasHelper::setupGraphicsState: unexpected mode" );
918                 break;
919         }
920 
921         if (renderState.DeviceColor.hasElements())
922             o_action.maARGBColor =
923                 mpDevice->getDeviceColorSpace()->convertToARGB(renderState.DeviceColor)[0];
924     }
925 
renderRecordedActions() const926     bool CanvasHelper::renderRecordedActions() const
927     {
928         for( const auto& rRecordedAction : *mpRecordedActions )
929         {
930             if( !rRecordedAction.maFunction( *this,
931                                              rRecordedAction.maTransform,
932                                              rRecordedAction.meSrcBlendMode,
933                                              rRecordedAction.meDstBlendMode,
934                                              rRecordedAction.maARGBColor,
935                                              rRecordedAction.maPolyPolys ) )
936                 return false;
937         }
938 
939         return true;
940     }
941 
getRecordedActionCount() const942     size_t CanvasHelper::getRecordedActionCount() const
943     {
944         return mpRecordedActions->size();
945     }
946 }
947 
948 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
949