Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  cairo_canvashelper.cxx   Sprache: C

 
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * This file is part of the LibreOffice project.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This file incorporates work covered by the following license notice:
 *
 *   Licensed to the Apache Software Foundation (ASF) under one or more
 *   contributor license agreements. See the NOTICE file distributed
 *   with this work for additional information regarding copyright
 *   ownership. The ASF licenses this file to you under the Apache
 *   License, Version 2.0 (the "License"); you may not use this file
 *   except in compliance with the License. You may obtain a copy of
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
 */


#include <sal/config.h>
#include <sal/log.hxx>

#include <algorithm>
#include <tuple>

#include <basegfx/point/b2dpoint.hxx>
#include <basegfx/polygon/b2dpolygon.hxx>
#include <basegfx/polygon/b2dpolypolygon.hxx>
#include <basegfx/utils/canvastools.hxx>
#include <basegfx/utils/keystoplerp.hxx>
#include <basegfx/utils/lerp.hxx>
#include <com/sun/star/rendering/ColorComponentTag.hpp>
#include <com/sun/star/rendering/ColorSpaceType.hpp>
#include <com/sun/star/rendering/CompositeOperation.hpp>
#include <com/sun/star/rendering/IntegerBitmapLayout.hpp>
#include <com/sun/star/rendering/PathCapType.hpp>
#include <com/sun/star/rendering/PathJoinType.hpp>
#include <com/sun/star/rendering/RenderingIntent.hpp>
#include <com/sun/star/rendering/TexturingMode.hpp>
#include <com/sun/star/rendering/XIntegerBitmapColorSpace.hpp>
#include <com/sun/star/util/Endianness.hpp>
#include <comphelper/sequence.hxx>
#include <cppuhelper/implbase.hxx>
#include <rtl/math.hxx>
#include <comphelper/diagnose_ex.hxx>
#include <vcl/bitmapex.hxx>
#include <vcl/BitmapTools.hxx>
#include <vcl/canvastools.hxx>
#include <vcl/virdev.hxx>

#include <canvas/canvastools.hxx>
#include <parametricpolypolygon.hxx>
#include <cairo.h>

#include "cairo_cachedbitmap.hxx"
#include "cairo_canvasbitmap.hxx"
#include "cairo_canvashelper.hxx"

using namespace ::cairo;
using namespace ::com::sun::star;

namespace cairocanvas
{
    CanvasHelper::CanvasHelper() :
        mpSurfaceProvider(nullptr),
        mpDevice(nullptr),
        mbHaveAlpha()
    {
    }

    void CanvasHelper::disposing()
    {
        mpSurface.reset();
        mpCairo.reset();
        mpVirtualDevice.disposeAndClear();
        mpDevice = nullptr;
        mpSurfaceProvider = nullptr;
    }

    void CanvasHelper::init( const ::basegfx::B2ISize&  rSizePixel,
                             SurfaceProvider&           rSurfaceProvider,
                             rendering::XGraphicDevice* pDevice )
    {
        maSize = rSizePixel;
        mpSurfaceProvider = &rSurfaceProvider;
        mpDevice = pDevice;
    }

    void CanvasHelper::setSize( const ::basegfx::B2ISize& rSize )
    {
        maSize = rSize;
    }

    void CanvasHelper::setSurface( const SurfaceSharedPtr& pSurface, bool bHasAlpha )
    {
        mbHaveAlpha = bHasAlpha;
        mpVirtualDevice.disposeAndClear();
        mpSurface = pSurface;
        mpCairo = pSurface->getCairo();
    }

    static void setColor( cairo_t* pCairo,
                          const uno::Sequence<double>& rColor )
    {
        if( rColor.getLength() > 3 )
        {
            cairo_set_source_rgba( pCairo,
                                   rColor[0],
                                   rColor[1],
                                   rColor[2],
                                   rColor[3] );
        }
        else if( rColor.getLength() == 3 )
            cairo_set_source_rgb( pCairo,
                                  rColor[0],
                                  rColor[1],
                                  rColor[2] );
    }

    void CanvasHelper::useStates( const rendering::ViewState& viewState,
                                  const rendering::RenderState& renderState,
                                  bool bSetColor )
    {
        cairo_matrix_t aViewMatrix;
        cairo_matrix_t aRenderMatrix;
        cairo_matrix_t aCombinedMatrix;

        cairo_matrix_init( &aViewMatrix,
                           viewState.AffineTransform.m00, viewState.AffineTransform.m10, viewState.AffineTransform.m01,
                           viewState.AffineTransform.m11, viewState.AffineTransform.m02, viewState.AffineTransform.m12);
        cairo_matrix_init( &aRenderMatrix,
                           renderState.AffineTransform.m00, renderState.AffineTransform.m10, renderState.AffineTransform.m01,
                           renderState.AffineTransform.m11, renderState.AffineTransform.m02, renderState.AffineTransform.m12);
        cairo_matrix_multiply( &aCombinedMatrix, &aRenderMatrix, &aViewMatrix);

        if( viewState.Clip.is() )
        {
            SAL_INFO( "canvas.cairo""view clip");

            aViewMatrix.x0 = basegfx::fround( aViewMatrix.x0 );
            aViewMatrix.y0 = basegfx::fround( aViewMatrix.y0 );
            cairo_set_matrix( mpCairo.get(), &aViewMatrix );
            doPolyPolygonPath( viewState.Clip, Clip );
        }

        aCombinedMatrix.x0 = basegfx::fround( aCombinedMatrix.x0 );
        aCombinedMatrix.y0 = basegfx::fround( aCombinedMatrix.y0 );
        cairo_set_matrix( mpCairo.get(), &aCombinedMatrix );

        if( renderState.Clip.is() )
        {
            SAL_INFO( "canvas.cairo""render clip BEGIN");

            doPolyPolygonPath( renderState.Clip, Clip );
            SAL_INFO( "canvas.cairo""render clip END");
        }

        if( bSetColor )
            setColor(mpCairo.get(),renderState.DeviceColor);

        cairo_operator_t compositingMode( CAIRO_OPERATOR_OVER );
        switch( renderState.CompositeOperation )
        {
            case rendering::CompositeOperation::CLEAR:
                compositingMode = CAIRO_OPERATOR_CLEAR;
                break;
            case rendering::CompositeOperation::SOURCE:
                compositingMode = CAIRO_OPERATOR_SOURCE;
                break;
            case rendering::CompositeOperation::DESTINATION:
            case rendering::CompositeOperation::UNDER:
                compositingMode = CAIRO_OPERATOR_DEST;
                break;
            case rendering::CompositeOperation::OVER:
                compositingMode = CAIRO_OPERATOR_OVER;
                break;
            case rendering::CompositeOperation::INSIDE:
                compositingMode = CAIRO_OPERATOR_IN;
                break;
            case rendering::CompositeOperation::INSIDE_REVERSE:
                compositingMode = CAIRO_OPERATOR_OUT;
                break;
            case rendering::CompositeOperation::OUTSIDE:
                compositingMode = CAIRO_OPERATOR_DEST_OVER;
                break;
            case rendering::CompositeOperation::OUTSIDE_REVERSE:
                compositingMode = CAIRO_OPERATOR_DEST_OUT;
                break;
            case rendering::CompositeOperation::ATOP:
                compositingMode = CAIRO_OPERATOR_ATOP;
                break;
            case rendering::CompositeOperation::ATOP_REVERSE:
                compositingMode = CAIRO_OPERATOR_DEST_ATOP;
                break;
            case rendering::CompositeOperation::XOR:
                compositingMode = CAIRO_OPERATOR_XOR;
                break;
            case rendering::CompositeOperation::ADD:
                compositingMode = CAIRO_OPERATOR_ADD;
                break;
            case rendering::CompositeOperation::SATURATE:
                compositingMode = CAIRO_OPERATOR_SATURATE;
                break;
        }
        cairo_set_operator( mpCairo.get(), compositingMode );
    }

    void CanvasHelper::clear()
    {
        SAL_INFO( "canvas.cairo""clear whole area: " << maSize.getWidth() << " x " << maSize.getHeight() );

        if( !mpCairo )
            return;

        cairo_save( mpCairo.get() );

        cairo_identity_matrix( mpCairo.get() );
        // this does not really differ from all-zero, as cairo
        // internally converts to premultiplied alpha. but anyway,
        // this keeps it consistent with the other canvas impls
        if( mbHaveAlpha )
            cairo_set_source_rgba( mpCairo.get(), 1.0, 1.0, 1.0, 0.0 );
        else
            cairo_set_source_rgb( mpCairo.get(), 1.0, 1.0, 1.0 );
        cairo_set_operator( mpCairo.get(), CAIRO_OPERATOR_SOURCE );

        cairo_rectangle( mpCairo.get(), 0, 0, maSize.getWidth(), maSize.getHeight() );
        cairo_fill( mpCairo.get() );

        cairo_restore( mpCairo.get() );
    }

    void CanvasHelper::drawLine( const rendering::XCanvas*      /*pCanvas*/,
                                 const geometry::RealPoint2D&   aStartPoint,
                                 const geometry::RealPoint2D&   aEndPoint,
                                 const rendering::ViewState&    viewState,
                                 const rendering::RenderState&  renderState )
    {
        if( !mpCairo )
            return;

        cairo_save( mpCairo.get() );

        cairo_set_line_width( mpCairo.get(), 1 );

        useStates( viewState, renderState, true );

        cairo_move_to( mpCairo.get(), aStartPoint.X + 0.5, aStartPoint.Y + 0.5 );
        cairo_line_to( mpCairo.get(), aEndPoint.X + 0.5, aEndPoint.Y + 0.5 );
        cairo_stroke( mpCairo.get() );

        cairo_restore( mpCairo.get() );
    }

    void CanvasHelper::drawBezier( const rendering::XCanvas*            ,
                                   const geometry::RealBezierSegment2D& aBezierSegment,
                                   const geometry::RealPoint2D&         aEndPoint,
                                   const rendering::ViewState&          viewState,
                                   const rendering::RenderState&        renderState )
    {
        if( !mpCairo )
            return;

        cairo_save( mpCairo.get() );

        cairo_set_line_width( mpCairo.get(), 1 );

        useStates( viewState, renderState, true );

        cairo_move_to( mpCairo.get(), aBezierSegment.Px + 0.5, aBezierSegment.Py + 0.5 );
        // tdf#99165 correction of control points not needed here, only hairlines drawn
        // (see cairo_set_line_width above)
        cairo_curve_to( mpCairo.get(),
                        aBezierSegment.C1x + 0.5, aBezierSegment.C1y + 0.5,
                        aBezierSegment.C2x + 0.5, aBezierSegment.C2y + 0.5,
                        aEndPoint.X + 0.5, aEndPoint.Y + 0.5 );
        cairo_stroke( mpCairo.get() );

        cairo_restore( mpCairo.get() );
    }

constexpr OUStringLiteral PARAMETRICPOLYPOLYGON_IMPLEMENTATION_NAME = u"Canvas::ParametricPolyPolygon";

    /** surfaceFromXBitmap Create a surface from XBitmap
     * @param xBitmap bitmap image that will be used for the surface
     * @param bHasAlpha will be set to true if resulting surface has alpha
     *
     * This is a helper function for the other surfaceFromXBitmap().
     * This function tries to create surface from xBitmap by checking if xBitmap is CanvasBitmap or SpriteCanvas.
     *
     * @return created surface or NULL
     **/

    static SurfaceSharedPtr surfaceFromXBitmap( const uno::Reference< rendering::XBitmap >&&nbsp;xBitmap )
    {
        CanvasBitmap* pBitmapImpl = dynamic_cast< CanvasBitmap* >( xBitmap.get() );
        if( pBitmapImpl )
            return pBitmapImpl->getSurface();

        SurfaceProvider* pSurfaceProvider = dynamic_cast<SurfaceProvider*>( xBitmap.get() );
        if( pSurfaceProvider )
            return pSurfaceProvider->getSurface();

        return SurfaceSharedPtr();
    }

    static ::BitmapEx bitmapExFromXBitmap( const uno::Reference< rendering::XBitmap >& xBitmap )
    {
        // TODO(F1): Add support for floating point bitmap formats
        uno::Reference<rendering::XIntegerReadOnlyBitmap> xIntBmp(xBitmap,
                                                                  uno::UNO_QUERY_THROW);
        ::BitmapEx aBmpEx = vcl::unotools::bitmapExFromXBitmap(xIntBmp);
        if( !aBmpEx.IsEmpty() )
            return aBmpEx;

        // TODO(F1): extract pixel from XBitmap interface
        ENSURE_OR_THROW( false,
                         "bitmapExFromXBitmap(): could not extract BitmapEx" );

        return ::BitmapEx();
    }

    /** surfaceFromXBitmap Create a surface from XBitmap
     * @param xBitmap bitmap image that will be used for the surface
     * @param rDevice reference to the device into which we want to draw
     * @param data will be filled with alpha data, if xBitmap is alpha/transparent image
     * @param bHasAlpha will be set to true if resulting surface has alpha
     *
     * This function tries various methods for creating a surface from xBitmap. It also uses
     * the helper function surfaceFromXBitmap( xBitmap, bHasAlpha )
     *
     * @return created surface or NULL
     **/

    static SurfaceSharedPtr surfaceFromXBitmap( const uno::Reference< rendering::XBitmap >&&nbsp;xBitmap, const SurfaceProviderRef& rSurfaceProvider, unsigned char*& data, bool& bHasAlpha )
    {
        bHasAlpha = xBitmap->hasAlpha();
        SurfaceSharedPtr pSurface = surfaceFromXBitmap( xBitmap );
        if( pSurface )
            data = nullptr;
        else
        {
            ::BitmapEx aBmpEx = bitmapExFromXBitmap(xBitmap);
            ::Bitmap aBitmap = aBmpEx.GetBitmap();

            // there's no pixmap for alpha bitmap. we might still
            // use rgb pixmap and only access alpha pixels the
            // slow way. now we just speedup rgb bitmaps
            if( !aBmpEx.IsAlpha() )
            {
                pSurface = rSurfaceProvider->createSurface( aBitmap );
                data = nullptr;
                bHasAlpha = false;
            }

            if( !pSurface )
            {
                tools::Long nWidth;
                tools::Long nHeight;
                vcl::bitmap::CanvasCairoExtractBitmapData(aBmpEx, aBitmap, data, bHasAlpha, nWidth, nHeight);

                pSurface = rSurfaceProvider->getOutputDevice()->CreateSurface(
                    CairoSurfaceSharedPtr(
                        cairo_image_surface_create_for_data(
                            data,
                            bHasAlpha ? CAIRO_FORMAT_ARGB32 : CAIRO_FORMAT_RGB24,
                            nWidth, nHeight, nWidth*4 ),
                        &cairo_surface_destroy) );

                SAL_INFO( "canvas.cairo","image: " << nWidth << " x " << nHeight << " alpha: " << bHasAlpha);
            }
        }

        return pSurface;
    }

    static void addColorStops( cairo_pattern_t* pPattern, const uno::Sequence< uno::Sequence< double > >& rColors, const uno::Sequence< double >& rStops, bool bReverseStops )
    {
        int i;

        OSL_ASSERT( rColors.getLength() == rStops.getLength() );

        for( i = 0; i < rColors.getLength(); i++ )
        {
            const uno::Sequence< double >& rColor( rColors[i] );
            float stop = bReverseStops ? 1 - rStops[i] : rStops[i];
            if( rColor.getLength() == 3 )
                cairo_pattern_add_color_stop_rgb( pPattern, stop, rColor[0], rColor[1], rColor[2] );
            else if( rColor.getLength() == 4 )
            {
                double alpha = rColor[3];
                // cairo expects premultiplied alpha
                cairo_pattern_add_color_stop_rgba( pPattern, stop, rColor[0]*alpha, rColor[1]*alpha, rColor[2]*alpha, alpha );
            }
        }
    }

    static uno::Sequence<double> lerp(const uno::Sequence<double>& rLeft, const uno::Sequence<double>& rRight, double fAlpha)
    {
        if( rLeft.getLength() == 3 )
        {
            return
            {
                basegfx::utils::lerp(rLeft[0],rRight[0],fAlpha),
                basegfx::utils::lerp(rLeft[1],rRight[1],fAlpha),
                basegfx::utils::lerp(rLeft[2],rRight[2],fAlpha)
            };
        }
        else if( rLeft.getLength() == 4 )
        {
            return
            {
                basegfx::utils::lerp(rLeft[0],rRight[0],fAlpha),
                basegfx::utils::lerp(rLeft[1],rRight[1],fAlpha),
                basegfx::utils::lerp(rLeft[2],rRight[2],fAlpha),
                basegfx::utils::lerp(rLeft[3],rRight[3],fAlpha)
            };
        }

        return {};
    }

    static cairo_pattern_t* patternFromParametricPolyPolygon( ::canvas::ParametricPolyPolygon const & rPolygon )
    {
        cairo_pattern_t* pPattern = nullptr;
        const ::canvas::ParametricPolyPolygon::Values& aValues = rPolygon.getValues();
        double x0, x1, y0, y1, cx, cy, r0, r1;

        switch( aValues.meType )
        {
            case ::canvas::ParametricPolyPolygon::GradientType::Linear:
                x0 = 0;
                y0 = 0;
                x1 = 1;
                y1 = 0;
                pPattern = cairo_pattern_create_linear( x0, y0, x1, y1 );
                addColorStops( pPattern, aValues.maColors, aValues.maStops, false );
                break;

            case ::canvas::ParametricPolyPolygon::GradientType::Elliptical:
                cx = 0;
                cy = 0;
                r0 = 0;
                r1 = 1;

                pPattern = cairo_pattern_create_radial( cx, cy, r0, cy, cy, r1 );
                addColorStops( pPattern, aValues.maColors, aValues.maStops, true );
                break;
            default:
                break;
        }

        return pPattern;
    }

    static void doOperation( Operation aOperation,
                             cairo_t* pCairo,
                             const uno::Sequence< rendering::Texture >* pTextures,
                             const SurfaceProviderRef& pDevice,
                             const basegfx::B2DRange& rBounds )
    {
        switch( aOperation )
        {
            case Fill:
                /* TODO: multitexturing */
                if( pTextures )
                {
                    const css::rendering::Texture& aTexture ( (*pTextures)[0] );
                    if( aTexture.Bitmap.is() )
                    {
                        unsigned char* data = nullptr;
                        bool bHasAlpha = false;
                        SurfaceSharedPtr pSurface = surfaceFromXBitmap( (*pTextures)[0].Bitmap, pDevice, data, bHasAlpha );

                        if( pSurface )
                        {
                            cairo_pattern_t* pPattern;

                            cairo_save( pCairo );

                            css::geometry::AffineMatrix2D aTransform( aTexture.AffineTransform );
                            cairo_matrix_t aScaleMatrix, aTextureMatrix, aScaledTextureMatrix;

                            cairo_matrix_init( &aTextureMatrix,
                                               aTransform.m00, aTransform.m10, aTransform.m01,
                                               aTransform.m11, aTransform.m02, aTransform.m12);

                            geometry::IntegerSize2D aSize = aTexture.Bitmap->getSize();

                            cairo_matrix_init_scale( &aScaleMatrix, 1.0/aSize.Width, 1.0/aSize.Height );
                            cairo_matrix_multiply( &aScaledTextureMatrix, &aScaleMatrix, &aTextureMatrix );
                            cairo_matrix_invert( &aScaledTextureMatrix );

                            // we don't care about repeat mode yet, so the workaround is disabled for now
                            pPattern = cairo_pattern_create_for_surface( pSurface->getCairoSurface().get() );

                            if( aTexture.RepeatModeX == rendering::TexturingMode::REPEAT &&
                                aTexture.RepeatModeY == rendering::TexturingMode::REPEAT )
                            {
                                cairo_pattern_set_extend( pPattern, CAIRO_EXTEND_REPEAT );
                            }
                            else if ( aTexture.RepeatModeX == rendering::TexturingMode::NONE &&
                                      aTexture.RepeatModeY == rendering::TexturingMode::NONE )
                            {
                                cairo_pattern_set_extend( pPattern, CAIRO_EXTEND_NONE );
                            }
                            else if ( aTexture.RepeatModeX == rendering::TexturingMode::CLAMP &&
                                      aTexture.RepeatModeY == rendering::TexturingMode::CLAMP )
                            {
                                cairo_pattern_set_extend( pPattern, CAIRO_EXTEND_PAD );
                            }

                            aScaledTextureMatrix.x0 = basegfx::fround( aScaledTextureMatrix.x0 );
                            aScaledTextureMatrix.y0 = basegfx::fround( aScaledTextureMatrix.y0 );

                            double x1, y1, x2, y2;
                            cairo_path_extents(pCairo, &x1, &y1, &x2, &y2);
                            aScaledTextureMatrix.x0 -= (x1 * aScaledTextureMatrix.xx);
                            aScaledTextureMatrix.y0 -= (y1 * aScaledTextureMatrix.yy);

                            cairo_pattern_set_matrix( pPattern, &aScaledTextureMatrix );

                            cairo_set_source( pCairo, pPattern );
                            if( !bHasAlpha )
                                cairo_set_operator( pCairo, CAIRO_OPERATOR_SOURCE );
                            cairo_fill( pCairo );

                            cairo_restore( pCairo );

                            cairo_pattern_destroy( pPattern );
                        }

                        if( data )
                            free( data );
                    }
                    else if( aTexture.Gradient.is() )
                    {
                        uno::Reference< lang::XServiceInfo > xRef( aTexture.Gradient, uno::UNO_QUERY );

                        SAL_INFO( "canvas.cairo""gradient fill" );
                        if( xRef.is() && xRef->getImplementationName() == PARAMETRICPOLYPOLYGON_IMPLEMENTATION_NAME )
                        {
                            // TODO(Q1): Maybe use dynamic_cast here

                            // TODO(E1): Return value
                            // TODO(F1): FillRule
                            SAL_INFO( "canvas.cairo""known implementation" );

                            ::canvas::ParametricPolyPolygon* pPolyImpl = static_cast< ::canvas::ParametricPolyPolygon* >( aTexture.Gradient.get() );
                            css::geometry::AffineMatrix2D aTransform( aTexture.AffineTransform );
                            cairo_matrix_t aTextureMatrix;

                            cairo_matrix_init( &aTextureMatrix,
                                               aTransform.m00, aTransform.m10, aTransform.m01,
                                               aTransform.m11, aTransform.m02, aTransform.m12);
                            if( pPolyImpl->getValues().meType == canvas::ParametricPolyPolygon::GradientType::Rectangular )
                            {
                                // no general path gradient yet in cairo; emulate then
                                cairo_save( pCairo );
                                cairo_clip( pCairo );

                                // fill bound rect with start color
                                cairo_rectangle( pCairo, rBounds.getMinX(), rBounds.getMinY(),
                                                 rBounds.getWidth(), rBounds.getHeight() );
                                setColor(pCairo,pPolyImpl->getValues().maColors[0]);
                                cairo_fill(pCairo);

                                cairo_transform( pCairo, &aTextureMatrix );

                                // longest line in gradient bound rect
                                const unsigned int nGradientSize(
                                    static_cast<unsigned int>(
                                        ::basegfx::B2DVector(rBounds.getMinimum() - rBounds.getMaximum()).getLength() + 1.0 ) );

                                // typical number for pixel of the same color (strip size)
                                const unsigned int nStripSize( nGradientSize < 50 ? 2 : 4 );

                                // use at least three steps, and at utmost the number of color
                                // steps
                                const unsigned int nStepCount(
                                    std::max(
                                        3U,
                                        std::min(
                                            nGradientSize / nStripSize,
                                            128U )) + 1 );

                                const uno::Sequence<double>* pColors=&pPolyImpl->getValues().maColors[0];
                                basegfx::utils::KeyStopLerp aLerper(pPolyImpl->getValues().maStops);
                                forunsigned int i=1; i<nStepCount; ++i )
                                {
                                    const double fT( i/double(nStepCount) );

                                    std::ptrdiff_t nIndex;
                                    double fAlpha;
                                    std::tie(nIndex,fAlpha)=aLerper.lerp(fT);

                                    setColor(pCairo, lerp(pColors[nIndex], pColors[nIndex+1], fAlpha));
                                    cairo_rectangle( pCairo, -1+fT, -1+fT, 2-2*fT, 2-2*fT );
                                    cairo_fill(pCairo);
                                }

                                cairo_restore( pCairo );
                            }
                            else
                            {
                                cairo_pattern_t* pPattern = patternFromParametricPolyPolygon( *pPolyImpl );

                                if( pPattern )
                                {
                                    SAL_INFO( "canvas.cairo""filling with pattern" );

                                    cairo_save( pCairo );

                                    cairo_transform( pCairo, &aTextureMatrix );
                                    cairo_set_source( pCairo, pPattern );
                                    cairo_fill( pCairo );
                                    cairo_restore( pCairo );

                                    cairo_pattern_destroy( pPattern );
                                }
                            }
                        }
                    }
                }
                else
                    cairo_fill( pCairo );
                SAL_INFO( "canvas.cairo""fill");
                break;
            case Stroke:
                cairo_stroke( pCairo );
                SAL_INFO( "canvas.cairo""stroke");
                break;
            case Clip:
                cairo_clip( pCairo );
                SAL_INFO( "canvas.cairo""clip");
                break;
        }
    }

    static void clipNULL( cairo_t *pCairo )
    {
        SAL_INFO( "canvas.cairo""clipNULL");
        cairo_matrix_t aOrigMatrix, aIdentityMatrix;

        /* we set identity matrix here to overcome bug in cairo 0.9.2
           where XCreatePixmap is called with zero width and height.

           it also reaches faster path in cairo clipping code.
        */

        cairo_matrix_init_identity( &aIdentityMatrix );
        cairo_get_matrix( pCairo, &aOrigMatrix );
        cairo_set_matrix( pCairo, &aIdentityMatrix );

        cairo_reset_clip( pCairo );
        cairo_rectangle( pCairo, 0, 0, 1, 1 );
        cairo_clip( pCairo );
        cairo_rectangle( pCairo, 2, 0, 1, 1 );
        cairo_clip( pCairo );

        /* restore the original matrix */
        cairo_set_matrix( pCairo, &aOrigMatrix );
    }

    void doPolyPolygonImplementation( const ::basegfx::B2DPolyPolygon& aPolyPolygon,
                                      Operation aOperation,
                                      cairo_t* pCairo,
                                      const uno::Sequence< rendering::Texture >* pTextures,
                                      const SurfaceProviderRef& pDevice,
                                      rendering::FillRule eFillrule )
    {
        if( pTextures )
            ENSURE_ARG_OR_THROW( pTextures->hasElements(),
                                 "CanvasHelper::fillTexturedPolyPolygon: empty texture sequence");

        bool bOpToDo = false;
        cairo_matrix_t aOrigMatrix, aIdentityMatrix;
        double nX, nY, nBX, nBY, nAX, nAY, nLastX(0.0), nLastY(0.0);

        cairo_get_matrix( pCairo, &aOrigMatrix );
        cairo_matrix_init_identity( &aIdentityMatrix );
        cairo_set_matrix( pCairo, &aIdentityMatrix );

        cairo_set_fill_rule( pCairo,
                             eFillrule == rendering::FillRule_EVEN_ODD ?
                             CAIRO_FILL_RULE_EVEN_ODD : CAIRO_FILL_RULE_WINDING );

        for( sal_uInt32 nPolygonIndex = 0; nPolygonIndex < aPolyPolygon.count(); nPolygonIndex++ )
        {
            const ::basegfx::B2DPolygon& aPolygon( aPolyPolygon.getB2DPolygon( nPolygonIndex ) );
            const sal_uInt32 nPointCount( aPolygon.count() );
            // to correctly render closed curves, need to output first
            // point twice (so output one additional point)
            const sal_uInt32 nExtendedPointCount( nPointCount +
                                                  int(aPolygon.isClosed() && aPolygon.areControlPointsUsed()) );

            if( nPointCount > 1)
            {
                bool bIsBezier = aPolygon.areControlPointsUsed();
                ::basegfx::B2DPoint aA, aB, aP;

                for( sal_uInt32 j=0; j < nExtendedPointCount; j++ )
                {
                    aP = aPolygon.getB2DPoint( j % nPointCount );

                    nX = aP.getX();
                    nY = aP.getY();
                    cairo_matrix_transform_point( &aOrigMatrix, &nX, &nY );

                    if (!bIsBezier && aOperation == Clip)
                    {
                        nX = basegfx::fround( nX );
                        nY = basegfx::fround( nY );
                    }

                    if( aOperation == Stroke )
                    {
                        nX += 0.5;
                        nY += 0.5;
                    }

                    if( j==0 )
                    {
                        cairo_move_to( pCairo, nX, nY );
                        SAL_INFO( "canvas.cairo""move to " << nX << "," << nY );
                    }
                    else
                    {
                        if( bIsBezier )
                        {
                            aA = aPolygon.getNextControlPoint( (j-1) % nPointCount );
                            aB = aPolygon.getPrevControlPoint( j % nPointCount );

                            nAX = aA.getX();
                            nAY = aA.getY();
                            nBX = aB.getX();
                            nBY = aB.getY();

                            cairo_matrix_transform_point( &aOrigMatrix, &nAX, &nAY );
                            cairo_matrix_transform_point( &aOrigMatrix, &nBX, &nBY );

                            if( aOperation == Stroke )
                            {
                                nAX += 0.5;
                                nAY += 0.5;
                                nBX += 0.5;
                                nBY += 0.5;
                            }

                            // tdf#99165 if the control points are 'empty', create the mathematical
                            // correct replacement ones to avoid problems with the graphical sub-system
                            // tdf#101026 The 1st attempt to create a mathematically correct replacement control
                            // vector was wrong. Best alternative is one as close as possible which means short.
                            if (basegfx::fTools::equal(nAX, nLastX) && basegfx::fTools::equal(nAY, nLastY))
                            {
                                nAX = nLastX + ((nBX - nLastX) * 0.0005);
                                nAY = nLastY + ((nBY - nLastY) * 0.0005);
                            }

                            if(basegfx::fTools::equal(nBX, nX) && basegfx::fTools::equal(nBY, nY))
                            {
                                nBX = nX + ((nAX - nX) * 0.0005);
                                nBY = nY + ((nAY - nY) * 0.0005);
                            }

                            cairo_curve_to( pCairo, nAX, nAY, nBX, nBY, nX, nY );
                        }
                        else
                        {
                            cairo_line_to( pCairo, nX, nY );
                            SAL_INFO( "canvas.cairo""line to " << nX << "," << nY );
                        }
                        bOpToDo = true;
                    }

                    nLastX = nX;
                    nLastY = nY;
                }

                if( aPolygon.isClosed() )
                    cairo_close_path( pCairo );

            }
            else
            {
                SAL_INFO( "canvas.cairo""empty polygon for op: " << aOperation );
                if( aOperation == Clip )
                {
                    clipNULL( pCairo );

                    return;
                }
            }
        }

        if( aOperation == Fill && pTextures )
        {
            cairo_set_matrix( pCairo, &aOrigMatrix );
            doOperation( aOperation, pCairo, pTextures, pDevice, aPolyPolygon.getB2DRange() );
            cairo_set_matrix( pCairo, &aIdentityMatrix );
        }

        if( bOpToDo && ( aOperation != Fill || !pTextures ) )
            doOperation( aOperation, pCairo, pTextures, pDevice, aPolyPolygon.getB2DRange() );

        cairo_set_matrix( pCairo, &aOrigMatrix );

        if( aPolyPolygon.count() == 0 && aOperation == Clip )
            clipNULL( pCairo );
    }

    void CanvasHelper::doPolyPolygonPath( const uno::Reference< rendering::XPolyPolygon2D >&&nbsp;xPolyPolygon,
                        Operation aOperation,
                        bool bNoLineJoin,
                        const uno::Sequence< rendering::Texture >* pTextures ) const
    {
        const ::basegfx::B2DPolyPolygon aPolyPoly(
            ::basegfx::unotools::b2DPolyPolygonFromXPolyPolygon2D(xPolyPolygon) );

        cairo_t* pCairo = mpCairo.get();

        if(bNoLineJoin && aOperation == Stroke)
        {
            // emulate rendering::PathJoinType::NONE by painting single edges
            for(sal_uInt32 a(0); a < aPolyPoly.count(); a++)
            {
                const basegfx::B2DPolygon& aCandidate(aPolyPoly.getB2DPolygon(a));
                const sal_uInt32 nPointCount(aCandidate.count());

                if(nPointCount)
                {
                    const sal_uInt32 nEdgeCount(aCandidate.isClosed() ? nPointCount: nPointCount - 1);
                    basegfx::B2DPolygon aEdge;
                    aEdge.append(aCandidate.getB2DPoint(0));
                    aEdge.append(basegfx::B2DPoint(0.0, 0.0));

                    for(sal_uInt32 b(0); b < nEdgeCount; b++)
                    {
                        const sal_uInt32 nNextIndex((b + 1) % nPointCount);
                        aEdge.setB2DPoint(1, aCandidate.getB2DPoint(nNextIndex));
                        aEdge.setNextControlPoint(0, aCandidate.getNextControlPoint(b % nPointCount));
                        aEdge.setPrevControlPoint(1, aCandidate.getPrevControlPoint(nNextIndex));

                        doPolyPolygonImplementation( basegfx::B2DPolyPolygon(aEdge),
                                                     aOperation,
                                                     pCairo, pTextures,
                                                     mpSurfaceProvider,
                                                     xPolyPolygon->getFillRule() );

                        // prepare next step
                        aEdge.setB2DPoint(0, aEdge.getB2DPoint(1));
                    }
                }
            }
        }
        else
        {
            doPolyPolygonImplementation( aPolyPoly, aOperation,
                                         pCairo, pTextures,
                                         mpSurfaceProvider,
                                         xPolyPolygon->getFillRule() );
        }
    }

    uno::Reference< rendering::XCachedPrimitive > CanvasHelper::drawPolyPolygon( const rendering::XCanvas*                          ,
                                                                                 const uno::Reference< rendering::XPolyPolygon2D >& xPolyPolygon,
                                                                                 const rendering::ViewState&                        viewState,
                                                                                 const rendering::RenderState&                      renderState )
    {
#ifdef CAIRO_CANVAS_PERF_TRACE
        struct timespec aTimer;
        mxDevice->startPerfTrace( &aTimer );
#endif

        if( mpCairo )
        {
            cairo_save( mpCairo.get() );

            cairo_set_line_width( mpCairo.get(), 1 );

            useStates( viewState, renderState, true );
            doPolyPolygonPath( xPolyPolygon, Stroke );

            cairo_restore( mpCairo.get() );
        }
        else
            SAL_INFO( "canvas.cairo""CanvasHelper called after it was disposed");

#ifdef CAIRO_CANVAS_PERF_TRACE
        mxDevice->stopPerfTrace( &aTimer, "drawPolyPolygon" );
#endif

        return uno::Reference< rendering::XCachedPrimitive >(nullptr);
    }

    uno::Reference< rendering::XCachedPrimitive > CanvasHelper::strokePolyPolygon( const rendering::XCanvas*                            ,
                                                                                   const uno::Reference< rendering::XPolyPolygon2D >&   xPolyPolygon,
                                                                                   const rendering::ViewState&                          viewState,
                                                                                   const rendering::RenderState&                        renderState,
                                                                                   const rendering::StrokeAttributes&                   strokeAttributes )
    {
#ifdef CAIRO_CANVAS_PERF_TRACE
        struct timespec aTimer;
        mxDevice->startPerfTrace( &aTimer );
#endif

        if( mpCairo )
        {
            cairo_save( mpCairo.get() );

            useStates( viewState, renderState, true );

            cairo_matrix_t aMatrix;
            cairo_get_matrix( mpCairo.get(), &aMatrix );
            double scaleFactorX = 1;
            double scaleFactorY = 0;
            cairo_matrix_transform_distance( &aMatrix, &scaleFactorX, &scaleFactorY );
            double scaleFactor = basegfx::B2DVector( scaleFactorX, scaleFactorY ).getLength();
            cairo_set_line_width( mpCairo.get(), strokeAttributes.StrokeWidth * scaleFactor );

            cairo_set_miter_limit( mpCairo.get(), strokeAttributes.MiterLimit );

            // FIXME: cairo doesn't handle end cap so far (rodo)
            switch( strokeAttributes.StartCapType )
            {
                case rendering::PathCapType::BUTT:
                    cairo_set_line_cap( mpCairo.get(), CAIRO_LINE_CAP_BUTT );
                    break;
                case rendering::PathCapType::ROUND:
                    cairo_set_line_cap( mpCairo.get(), CAIRO_LINE_CAP_ROUND );
                    break;
                case rendering::PathCapType::SQUARE:
                    cairo_set_line_cap( mpCairo.get(), CAIRO_LINE_CAP_SQUARE );
                    break;
            }

            bool bNoLineJoin(false);

            switch( strokeAttributes.JoinType )
            {
                case rendering::PathJoinType::NONE:
                    bNoLineJoin = true;
                    [[fallthrough]]; // cairo doesn't have join type NONE so we use MITER as it's pretty close
                case rendering::PathJoinType::MITER:
                    cairo_set_line_join( mpCairo.get(), CAIRO_LINE_JOIN_MITER );
                    break;
                case rendering::PathJoinType::ROUND:
                    cairo_set_line_join( mpCairo.get(), CAIRO_LINE_JOIN_ROUND );
                    break;
                case rendering::PathJoinType::BEVEL:
                    cairo_set_line_join( mpCairo.get(), CAIRO_LINE_JOIN_BEVEL );
                    break;
            }

            //tdf#103026 If the scaling is 0, then all dashes become zero so
            //cairo will set the cairo_t status to CAIRO_STATUS_INVALID_DASH
            //and no further drawing will occur
            if (strokeAttributes.DashArray.hasElements() && scaleFactor > 0.0)
            {
                auto aDashArray(comphelper::sequenceToContainer<std::vector<double>>(strokeAttributes.DashArray));
                for (auto& rDash : aDashArray)
                    rDash *= scaleFactor;
                cairo_set_dash(mpCairo.get(), aDashArray.data(), aDashArray.size(), 0);
            }

            // TODO(rodo) use LineArray of strokeAttributes

            doPolyPolygonPath( xPolyPolygon, Stroke, bNoLineJoin );

            cairo_restore( mpCairo.get() );
        }
        else
            SAL_INFO( "canvas.cairo""CanvasHelper called after it was disposed");

#ifdef CAIRO_CANVAS_PERF_TRACE
        mxDevice->stopPerfTrace( &aTimer, "strokePolyPolygon" );
#endif

        // TODO(P1): Provide caching here.
        return uno::Reference< rendering::XCachedPrimitive >(nullptr);
    }

    uno::Reference< rendering::XCachedPrimitive > CanvasHelper::strokeTexturedPolyPolygonconst rendering::XCanvas*                            ,
                                                                                           const uno::Reference< rendering::XPolyPolygon2D >&   /*xPolyPolygon*/,
                                                                                           const rendering::ViewState&                          /*viewState*/,
                                                                                           const rendering::RenderState&                        /*renderState*/,
                                                                                           const uno::Sequence< rendering::Texture >&           /*textures*/,
                                                                                           const rendering::StrokeAttributes&                   /*strokeAttributes*/ )
    {
        // TODO
        return uno::Reference< rendering::XCachedPrimitive >(nullptr);
    }

    uno::Reference< rendering::XCachedPrimitive > CanvasHelper::strokeTextureMappedPolyPolygon( const rendering::XCanvas*                           ,
                                                                                                const uno::Reference< rendering::XPolyPolygon2D >&  /*xPolyPolygon*/,
                                                                                                const rendering::ViewState&                         /*viewState*/,
                                                                                                const rendering::RenderState&                       /*renderState*/,
                                                                                                const uno::Sequence< rendering::Texture >&          /*textures*/,
                                                                                                const uno::Reference< geometry::XMapping2D >&       /*xMapping*/,
                                                                                                const rendering::StrokeAttributes&                  /*strokeAttributes*/ )
    {
        // TODO
        return uno::Reference< rendering::XCachedPrimitive >(nullptr);
    }

    uno::Reference< rendering::XPolyPolygon2D >   CanvasHelper::queryStrokeShapes( const rendering::XCanvas*                            ,
                                                                                   const uno::Reference< rendering::XPolyPolygon2D >&   /*xPolyPolygon*/,
                                                                                   const rendering::ViewState&                          /*viewState*/,
                                                                                   const rendering::RenderState&                        /*renderState*/,
                                                                                   const rendering::StrokeAttributes&                   /*strokeAttributes*/ )
    {
        // TODO
        return uno::Reference< rendering::XPolyPolygon2D >(nullptr);
    }

    uno::Reference< rendering::XCachedPrimitive > CanvasHelper::fillPolyPolygon( const rendering::XCanvas*                          ,
                                                                                 const uno::Reference< rendering::XPolyPolygon2D >& xPolyPolygon,
                                                                                 const rendering::ViewState&                        viewState,
                                                                                 const rendering::RenderState&                      renderState )
    {
#ifdef CAIRO_CANVAS_PERF_TRACE
        struct timespec aTimer;
        mxDevice->startPerfTrace( &aTimer );
#endif

        if( mpCairo )
        {
            cairo_save( mpCairo.get() );

            useStates( viewState, renderState, true );
            doPolyPolygonPath( xPolyPolygon, Fill );

            cairo_restore( mpCairo.get() );
        }
        else
            SAL_INFO( "canvas.cairo""CanvasHelper called after it was disposed");

#ifdef CAIRO_CANVAS_PERF_TRACE
        mxDevice->stopPerfTrace( &aTimer, "fillPolyPolygon" );
#endif

        return uno::Reference< rendering::XCachedPrimitive >(nullptr);
    }

    uno::Reference< rendering::XCachedPrimitive > CanvasHelper::fillTexturedPolyPolygon( const rendering::XCanvas*                          ,
                                                                                         const uno::Reference< rendering::XPolyPolygon2D >& xPolyPolygon,
                                                                                         const rendering::ViewState&                        viewState,
                                                                                         const rendering::RenderState&                      renderState,
                                                                                         const uno::Sequence< rendering::Texture >&         textures )
    {
        if( mpCairo )
        {
            cairo_save( mpCairo.get() );

            useStates( viewState, renderState, true );
            doPolyPolygonPath( xPolyPolygon, Fill, false, &textures );

            cairo_restore( mpCairo.get() );
        }

        return uno::Reference< rendering::XCachedPrimitive >(nullptr);
    }

    uno::Reference< rendering::XCachedPrimitive > CanvasHelper::fillTextureMappedPolyPolygon( const rendering::XCanvas*                             ,
                                                                                              const uno::Reference< rendering::XPolyPolygon2D >&    /*xPolyPolygon*/,
                                                                                              const rendering::ViewState&                           /*viewState*/,
                                                                                              const rendering::RenderState&                         /*renderState*/,
                                                                                              const uno::Sequence< rendering::Texture >&            /*textures*/,
                                                                                              const uno::Reference< geometry::XMapping2D >&         /*xMapping*/ )
    {
        // TODO
        return uno::Reference< rendering::XCachedPrimitive >(nullptr);
    }

    uno::Reference< rendering::XCachedPrimitive > CanvasHelper::implDrawBitmapSurface( const rendering::XCanvas*        pCanvas,
                                                                                       const SurfaceSharedPtr&          pInputSurface,
                                                                                       const rendering::ViewState&      viewState,
                                                                                       const rendering::RenderState&    renderState,
                                                                                       const geometry::IntegerSize2D&   rSize,
                                                                                       bool                             bModulateColors,
                                                                                       bool                             bHasAlpha )
    {
        SurfaceSharedPtr pSurface=pInputSurface;
        uno::Reference< rendering::XCachedPrimitive > rv;
        geometry::IntegerSize2D aBitmapSize = rSize;

        if( mpCairo )
        {
            cairo_save( mpCairo.get() );

            cairo_rectangle( mpCairo.get(), 0, 0, maSize.getWidth(), maSize.getHeight() );
            cairo_clip( mpCairo.get() );

            useStates( viewState, renderState, true );

            cairo_matrix_t aMatrix;

            cairo_get_matrix( mpCairo.get(), &aMatrix );
            if( ! ::rtl::math::approxEqual( aMatrix.xx, 1 ) &&
                ! ::rtl::math::approxEqual( aMatrix.yy, 1 ) &&
                ::rtl::math::approxEqual( aMatrix.x0, 0 ) &&
                ::rtl::math::approxEqual( aMatrix.y0, 0 ) &&
                basegfx::fround( rSize.Width * aMatrix.xx ) > 8 &&
                basegfx::fround( rSize.Height* aMatrix.yy ) > 8 )
            {
                double dWidth, dHeight;

                dWidth = basegfx::fround( rSize.Width * aMatrix.xx );
                dHeight = basegfx::fround( rSize.Height* aMatrix.yy );
                aBitmapSize.Width = static_cast<sal_Int32>( dWidth );
                aBitmapSize.Height = static_cast<sal_Int32>( dHeight );

                SurfaceSharedPtr pScaledSurface = mpSurfaceProvider->createSurface(
                    ::basegfx::B2ISize( aBitmapSize.Width, aBitmapSize.Height ),
                    bHasAlpha ? CAIRO_CONTENT_COLOR_ALPHA : CAIRO_CONTENT_COLOR );
                CairoSharedPtr pCairo = pScaledSurface->getCairo();

                cairo_set_operator( pCairo.get(), CAIRO_OPERATOR_SOURCE );
                // add 0.5px to size to avoid rounding errors in cairo, leading sometimes to random data on the image right/bottom borders
                cairo_scale( pCairo.get(), (dWidth+0.5)/rSize.Width, (dHeight+0.5)/rSize.Height );
                cairo_set_source_surface( pCairo.get(), pSurface->getCairoSurface().get(), 0, 0 );
                cairo_paint( pCairo.get() );

                pSurface = std::move(pScaledSurface);

                aMatrix.xx = aMatrix.yy = 1;
                cairo_set_matrix( mpCairo.get(), &aMatrix );

                rv.set(
                    new CachedBitmap( pSurface, viewState, renderState,
                                      // cast away const, need to
                                      // change refcount (as this is
                                      // ~invisible to client code,
                                      // still logically const)
                                      const_cast< rendering::XCanvas* >(pCanvas)) );
            }

            if( !bHasAlpha && mbHaveAlpha )
            {
                double x, y, width, height;

                x = y = 0;
                width = aBitmapSize.Width;
                height = aBitmapSize.Height;
                cairo_matrix_transform_point( &aMatrix, &x, &y );
                cairo_matrix_transform_distance( &aMatrix, &width, &height );

                // in case the bitmap doesn't have alpha and covers whole area
                // we try to change surface to plain rgb
                SAL_INFO( "canvas.cairo","chance to change surface to rgb, " << x << ", " << y << ", " << width << " x " << height << " (" << maSize.getWidth() << " x " << maSize.getHeight() << ")" );
                if( x <= 0 && y <= 0 && x + width >= maSize.getWidth() && y + height >= maSize.getHeight() )
                {
                    SAL_INFO( "canvas.cairo","trying to change surface to rgb");
                    if( mpSurfaceProvider ) {
                        SurfaceSharedPtr pNewSurface = mpSurfaceProvider->changeSurface();

                        if( pNewSurface )
                            setSurface( pNewSurface, false );

                        // set state to new mpCairo.get()
                        useStates( viewState, renderState, true );
                        // use the possibly modified matrix
                        cairo_set_matrix( mpCairo.get(), &aMatrix );
                    }
                }
            }

            cairo_set_source_surface( mpCairo.get(), pSurface->getCairoSurface().get(), 0, 0 );
            if( !bHasAlpha &&
                ::rtl::math::approxEqual( aMatrix.xx, 1 ) &&
                ::rtl::math::approxEqual( aMatrix.yy, 1 ) &&
                ::rtl::math::approxEqual( aMatrix.x0, 0 ) &&
                ::rtl::math::approxEqual( aMatrix.y0, 0 ) )
                cairo_set_operator( mpCairo.get(), CAIRO_OPERATOR_SOURCE );
            cairo_pattern_set_extend( cairo_get_source(mpCairo.get()), CAIRO_EXTEND_PAD );
            cairo_rectangle( mpCairo.get(), 0, 0, aBitmapSize.Width, aBitmapSize.Height );
            cairo_clip( mpCairo.get() );

            // Use cairo_matrix_transform_distance() to determine the scaling, as that works even if
            // the matrix also has rotation.
            double fPixelWidth = rSize.Width;
            double fPixelHeight = rSize.Height;
            cairo_matrix_transform_distance(&aMatrix, &fPixelWidth, &fPixelHeight);
            int nPixelWidth = std::round(fPixelWidth);
            int nPixelHeight = std::round(fPixelHeight);
            if (std::abs(nPixelWidth) > 0 && std::abs(nPixelHeight) > 0)
            {
                // Only render the image if it's at least 1x1 px sized.
                if (bModulateColors)
                    cairo_paint_with_alpha(mpCairo.get(), renderState.DeviceColor[3]);
                else
                {
                    cairo_paint(mpCairo.get());
                    if (cairo_status(mpCairo.get()) != CAIRO_STATUS_SUCCESS)
                    {
                        SAL_WARN("canvas.cairo""cairo_paint() failed: " << cairo_status_to_string(
                                                     cairo_status(mpCairo.get())));
                    }
                }
            }
            cairo_restore( mpCairo.get() );
        }
        else
            SAL_INFO( "canvas.cairo""CanvasHelper called after it was disposed");

        return rv; // uno::Reference< rendering::XCachedPrimitive >(NULL);
    }

    uno::Reference< rendering::XCachedPrimitive > CanvasHelper::drawBitmap( const rendering::XCanvas*                   pCanvas,
                                                                            const uno::Reference< rendering::XBitmap >& xBitmap,
                                                                            const rendering::ViewState&                 viewState,
                                                                            const rendering::RenderState&               renderState )
    {
#ifdef CAIRO_CANVAS_PERF_TRACE
        struct timespec aTimer;
        mxDevice->startPerfTrace( &aTimer );
#endif

        uno::Reference< rendering::XCachedPrimitive > rv;
        unsigned char* data = nullptr;
        bool bHasAlpha = false;
        SurfaceSharedPtr pSurface = surfaceFromXBitmap( xBitmap, mpSurfaceProvider, data, bHasAlpha );
        geometry::IntegerSize2D aSize = xBitmap->getSize();

        if( pSurface )
        {
            rv = implDrawBitmapSurface( pCanvas, pSurface, viewState, renderState, aSize, false, bHasAlpha );

            if( data )
                free( data );
        }
        else
            rv.clear();

#ifdef CAIRO_CANVAS_PERF_TRACE
        mxDevice->stopPerfTrace( &aTimer, "drawBitmap" );
#endif

        return rv;
    }

    uno::Reference< rendering::XCachedPrimitive > CanvasHelper::drawBitmapModulated( const rendering::XCanvas*                      pCanvas,
                                                                                     const uno::Reference< rendering::XBitmap >&    xBitmap,
                                                                                     const rendering::ViewState&                    viewState,
                                                                                     const rendering::RenderState&                  renderState )
    {
#ifdef CAIRO_CANVAS_PERF_TRACE
        struct timespec aTimer;
        mxDevice->startPerfTrace( &aTimer );
#endif

        uno::Reference< rendering::XCachedPrimitive > rv;
        unsigned char* data = nullptr;
        bool bHasAlpha = false;
        SurfaceSharedPtr pSurface = surfaceFromXBitmap( xBitmap, mpSurfaceProvider, data, bHasAlpha );
        geometry::IntegerSize2D aSize = xBitmap->getSize();

        if( pSurface )
        {
            rv = implDrawBitmapSurface( pCanvas, pSurface, viewState, renderState, aSize, true, bHasAlpha );

            if( data )
                free( data );
        }
        else
            rv.clear();

#ifdef CAIRO_CANVAS_PERF_TRACE
        mxDevice->stopPerfTrace( &aTimer, "drawBitmap" );
#endif

        return rv;
    }


    geometry::IntegerSize2D CanvasHelper::getSize() const
    {
        if( !mpSurfaceProvider )
            return geometry::IntegerSize2D(1, 1); // we're disposed

        return ::basegfx::unotools::integerSize2DFromB2ISize( maSize );
    }

    uno::Reference< rendering::XBitmap > CanvasHelper::getScaledBitmap( const geometry::RealSize2D& newSize,
                                                                        bool                       /*beFast*/ )
    {
#ifdef CAIRO_CANVAS_PERF_TRACE
        struct timespec aTimer;
        mxDevice->startPerfTrace( &aTimer );
#endif

        if( mpCairo )
        {
            return uno::Reference< rendering::XBitmap >( new CanvasBitmap( ::basegfx::B2ISize( ::canvas::tools::roundUp( newSize.Width ),
                                                                                               ::canvas::tools::roundUp( newSize.Height ) ),
                                                                           mpSurfaceProvider, mpDevice, false ) );
        }
        else
            SAL_INFO( "canvas.cairo""CanvasHelper called after it was disposed");

#ifdef CAIRO_CANVAS_PERF_TRACE
        mxDevice->stopPerfTrace( &aTimer, "getScaledBitmap" );
#endif

        return uno::Reference< rendering::XBitmap >();
    }

    uno::Sequence< sal_Int8 > CanvasHelper::getData( rendering::IntegerBitmapLayout&     aLayout,
                                                     const geometry::IntegerRectangle2D& rect )
    {
        if( mpCairo )
        {
            const sal_Int32 nWidth( rect.X2 - rect.X1 );
            const sal_Int32 nHeight( rect.Y2 - rect.Y1 );
            const cairo_format_t eFormat( mbHaveAlpha ? CAIRO_FORMAT_ARGB32 : CAIRO_FORMAT_RGB24 );
            uno::Sequence< sal_Int8 > aRes( 4*nWidth*nHeight );
            sal_Int8* pData = aRes.getArray();
            cairo_surface_t* pImageSurface = cairo_image_surface_create_for_data( reinterpret_cast<unsigned char *>(pData),
                                                                                  eFormat,
                                                                                  nWidth, nHeight, 4*nWidth );
            cairo_t* pCairo = cairo_create( pImageSurface );
            cairo_set_source_surface( pCairo, mpSurface->getCairoSurface().get(), -rect.X1, -rect.Y1);
            cairo_paint( pCairo );
            cairo_destroy( pCairo );
            cairo_surface_destroy( pImageSurface );

            aLayout = impl_getMemoryLayout( nWidth, nHeight );

            return aRes;
        }

        return uno::Sequence< sal_Int8 >();
    }

    uno::Sequence< sal_Int8 > CanvasHelper::getPixel( rendering::IntegerBitmapLayout&   /*bitmapLayout*/,
                                                      const geometry::IntegerPoint2D&   /*pos*/ )
    {
        return uno::Sequence< sal_Int8 >();
    }

    namespace
    {
        class CairoColorSpace : public cppu::WeakImplHelper< css::rendering::XIntegerBitmapColorSpace >
        {
        private:
            uno::Sequence< sal_Int8 >  maComponentTags;
            uno::Sequence< sal_Int32 > maBitCounts;

            virtual ::sal_Int8 SAL_CALL getType(  ) override
            {
                return rendering::ColorSpaceType::RGB;
            }
            virtual uno::Sequence< ::sal_Int8 > SAL_CALL getComponentTags(  ) override
            {
                return maComponentTags;
            }
            virtual ::sal_Int8 SAL_CALL getRenderingIntent(  ) override
            {
                return rendering::RenderingIntent::PERCEPTUAL;
            }
            virtual uno::Sequence< beans::PropertyValue > SAL_CALL getProperties(  ) override
            {
                return uno::Sequence< beans::PropertyValue >();
            }
            virtual uno::Sequence< double > SAL_CALL convertColorSpace( const uno::Sequence< double >& ;deviceColor,
                                                                        const uno::Reference< rendering::XColorSpace >& targetColorSpace ) override
            {
                // TODO(P3): if we know anything about target
                // colorspace, this can be greatly sped up
                uno::Sequence<rendering::ARGBColor> aIntermediate(
                    convertToARGB(deviceColor));
                return targetColorSpace->convertFromARGB(aIntermediate);
            }
            virtual uno::Sequence< rendering::RGBColor > SAL_CALL convertToRGB( const uno::Sequence< double >& deviceColor ) override
            {
                const double*  pIn( deviceColor.getConstArray() );
                const std::size_t nLen( deviceColor.getLength() );
                ENSURE_ARG_OR_THROW2(nLen%4==0,
                                     "number of channels no multiple of 4",
                                     static_cast<rendering::XColorSpace*>(this), 0);

                uno::Sequence< rendering::RGBColor > aRes(nLen/4);
                rendering::RGBColor* pOut( aRes.getArray() );
                for( std::size_t i=0; i<nLen; i+=4 )
                {
                    const double fAlpha(pIn[3]);
                    if( fAlpha == 0.0 )
                        *pOut++ = rendering::RGBColor(0.0, 0.0, 0.0);
                    else
                        *pOut++ = rendering::RGBColor(pIn[2]/fAlpha,pIn[1]/fAlpha,pIn[0]/fAlpha);
                    pIn += 4;
                }
                return aRes;
            }
            virtual uno::Sequence< rendering::ARGBColor > SAL_CALL convertToARGB( const uno::Sequence< double >& deviceColor ) override
            {
                const double*  pIn( deviceColor.getConstArray() );
                const std::size_t nLen( deviceColor.getLength() );
                ENSURE_ARG_OR_THROW2(nLen%4==0,
                                     "number of channels no multiple of 4",
                                     static_cast<rendering::XColorSpace*>(this), 0);

                uno::Sequence< rendering::ARGBColor > aRes(nLen/4);
                rendering::ARGBColor* pOut( aRes.getArray() );
                for( std::size_t i=0; i<nLen; i+=4 )
                {
                    const double fAlpha(pIn[3]);
                    if( fAlpha == 0.0 )
                        *pOut++ = rendering::ARGBColor(0.0, 0.0, 0.0, 0.0);
                    else
                        *pOut++ = rendering::ARGBColor(fAlpha,pIn[2]/fAlpha,pIn[1]/fAlpha,pIn[0]/fAlpha);
                    pIn += 4;
                }
                return aRes;
            }
            virtual uno::Sequence< rendering::ARGBColor > SAL_CALL convertToPARGB( const uno::Sequence< double >& deviceColor ) override
            {
                const double*  pIn( deviceColor.getConstArray() );
                const std::size_t nLen( deviceColor.getLength() );
                ENSURE_ARG_OR_THROW2(nLen%4==0,
                                     "number of channels no multiple of 4",
                                     static_cast<rendering::XColorSpace*>(this), 0);

                uno::Sequence< rendering::ARGBColor > aRes(nLen/4);
                rendering::ARGBColor* pOut( aRes.getArray() );
                for( std::size_t i=0; i<nLen; i+=4 )
                {
                    *pOut++ = rendering::ARGBColor(pIn[3],pIn[2],pIn[1],pIn[1]);
                    pIn += 4;
                }
                return aRes;
            }
            virtual uno::Sequence< double > SAL_CALL convertFromRGB( const uno::Sequence< rendering::RGBColor >& rgbColor ) override
--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=93 H=92 G=92

¤ Dauer der Verarbeitung: 0.41 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge