/* -*- 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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& 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: compositingMode = CAIRO_OPERATOR_DEST; break; case rendering::CompositeOperation::OVER: compositingMode = CAIRO_OPERATOR_OVER; break; case rendering::CompositeOperation::UNDER: compositingMode = CAIRO_OPERATOR_DEST; 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 >& xBitmap ) { CanvasBitmap* pBitmapImpl = dynamic_cast< CanvasBitmap* >( xBitmap.get() ); if( pBitmapImpl ) return pBitmapImpl->getSurface(); SurfaceProvider* pSurfaceProvider = dynamic_cast( 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 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 >& 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 lerp(const uno::Sequence& rLeft, const uno::Sequence& 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( ::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* pColors=&pPolyImpl->getValues().maColors[0]; basegfx::utils::KeyStopLerp aLerper(pPolyImpl->getValues().maStops); for( unsigned int i=1; i* 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 >& xPolyPolygon, Operation aOperation, bool bNoLineJoin, const uno::Sequence< rendering::Texture >* pTextures ) const { const ::basegfx::B2DPolyPolygon& rPolyPoly( ::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 < rPolyPoly.count(); a++) { const basegfx::B2DPolygon& aCandidate(rPolyPoly.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( rPolyPoly, 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>(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::strokeTexturedPolyPolygon( const 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( dWidth ); aBitmapSize.Height = static_cast( 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 = 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(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 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(this), 0); uno::Sequence< rendering::RGBColor > aRes(nLen/4); rendering::RGBColor* pOut( aRes.getArray() ); for( std::size_t i=0; i 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(this), 0); uno::Sequence< rendering::ARGBColor > aRes(nLen/4); rendering::ARGBColor* pOut( aRes.getArray() ); for( std::size_t i=0; i 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(this), 0); uno::Sequence< rendering::ARGBColor > aRes(nLen/4); rendering::ARGBColor* pOut( aRes.getArray() ); for( std::size_t i=0; i SAL_CALL convertFromRGB( const uno::Sequence< rendering::RGBColor >& rgbColor ) override { const rendering::RGBColor* pIn( rgbColor.getConstArray() ); const std::size_t nLen( rgbColor.getLength() ); uno::Sequence< double > aRes(nLen*4); double* pColors=aRes.getArray(); for( std::size_t i=0; iBlue; *pColors++ = pIn->Green; *pColors++ = pIn->Red; *pColors++ = 1.0; ++pIn; } return aRes; } virtual uno::Sequence< double > SAL_CALL convertFromARGB( const uno::Sequence< rendering::ARGBColor >& rgbColor ) override { const rendering::ARGBColor* pIn( rgbColor.getConstArray() ); const std::size_t nLen( rgbColor.getLength() ); uno::Sequence< double > aRes(nLen*4); double* pColors=aRes.getArray(); for( std::size_t i=0; iAlpha*pIn->Blue; *pColors++ = pIn->Alpha*pIn->Green; *pColors++ = pIn->Alpha*pIn->Red; *pColors++ = pIn->Alpha; ++pIn; } return aRes; } virtual uno::Sequence< double > SAL_CALL convertFromPARGB( const uno::Sequence< rendering::ARGBColor >& rgbColor ) override { const rendering::ARGBColor* pIn( rgbColor.getConstArray() ); const std::size_t nLen( rgbColor.getLength() ); uno::Sequence< double > aRes(nLen*4); double* pColors=aRes.getArray(); for( std::size_t i=0; iBlue; *pColors++ = pIn->Green; *pColors++ = pIn->Red; *pColors++ = pIn->Alpha; ++pIn; } return aRes; } // XIntegerBitmapColorSpace virtual ::sal_Int32 SAL_CALL getBitsPerPixel( ) override { return 32; } virtual uno::Sequence< ::sal_Int32 > SAL_CALL getComponentBitCounts( ) override { return maBitCounts; } virtual ::sal_Int8 SAL_CALL getEndianness( ) override { return util::Endianness::LITTLE; } virtual uno::Sequence SAL_CALL convertFromIntegerColorSpace( const uno::Sequence< ::sal_Int8 >& deviceColor, const uno::Reference< rendering::XColorSpace >& targetColorSpace ) override { if( dynamic_cast(targetColorSpace.get()) ) { const sal_Int8* 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(this), 0); uno::Sequence aRes(nLen); double* pOut( aRes.getArray() ); for( std::size_t i=0; i aIntermediate( convertIntegerToARGB(deviceColor)); return targetColorSpace->convertFromARGB(aIntermediate); } } virtual uno::Sequence< ::sal_Int8 > SAL_CALL convertToIntegerColorSpace( const uno::Sequence< ::sal_Int8 >& deviceColor, const uno::Reference< rendering::XIntegerBitmapColorSpace >& targetColorSpace ) override { if( dynamic_cast(targetColorSpace.get()) ) { // it's us, so simply pass-through the data return deviceColor; } else { // TODO(P3): if we know anything about target // colorspace, this can be greatly sped up uno::Sequence aIntermediate( convertIntegerToARGB(deviceColor)); return targetColorSpace->convertIntegerFromARGB(aIntermediate); } } virtual uno::Sequence< rendering::RGBColor > SAL_CALL convertIntegerToRGB( const uno::Sequence< ::sal_Int8 >& deviceColor ) override { const sal_Int8* 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(this), 0); uno::Sequence< rendering::RGBColor > aRes(nLen/4); rendering::RGBColor* pOut( aRes.getArray() ); for( std::size_t i=0; i(pIn[3])); if( fAlpha ) *pOut++ = rendering::RGBColor( pIn[2]/fAlpha, pIn[1]/fAlpha, pIn[0]/fAlpha); else *pOut++ = rendering::RGBColor(0,0,0); pIn += 4; } return aRes; } virtual uno::Sequence< rendering::ARGBColor > SAL_CALL convertIntegerToARGB( const uno::Sequence< ::sal_Int8 >& deviceColor ) override { const sal_Int8* 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(this), 0); uno::Sequence< rendering::ARGBColor > aRes(nLen/4); rendering::ARGBColor* pOut( aRes.getArray() ); for( std::size_t i=0; i(pIn[3])); if( fAlpha ) *pOut++ = rendering::ARGBColor( fAlpha/255.0, pIn[2]/fAlpha, pIn[1]/fAlpha, pIn[0]/fAlpha); else *pOut++ = rendering::ARGBColor(0,0,0,0); pIn += 4; } return aRes; } virtual uno::Sequence< rendering::ARGBColor > SAL_CALL convertIntegerToPARGB( const uno::Sequence< ::sal_Int8 >& deviceColor ) override { const sal_Int8* 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(this), 0); uno::Sequence< rendering::ARGBColor > aRes(nLen/4); rendering::ARGBColor* pOut( aRes.getArray() ); for( std::size_t i=0; i SAL_CALL convertIntegerFromRGB( const uno::Sequence< rendering::RGBColor >& rgbColor ) override { const rendering::RGBColor* pIn( rgbColor.getConstArray() ); const std::size_t nLen( rgbColor.getLength() ); uno::Sequence< sal_Int8 > aRes(nLen*4); sal_Int8* pColors=aRes.getArray(); for( std::size_t i=0; iBlue); *pColors++ = vcl::unotools::toByteColor(pIn->Green); *pColors++ = vcl::unotools::toByteColor(pIn->Red); *pColors++ = -1; ++pIn; } return aRes; } virtual uno::Sequence< ::sal_Int8 > SAL_CALL convertIntegerFromARGB( const uno::Sequence< rendering::ARGBColor >& rgbColor ) override { const rendering::ARGBColor* pIn( rgbColor.getConstArray() ); const std::size_t nLen( rgbColor.getLength() ); uno::Sequence< sal_Int8 > aRes(nLen*4); sal_Int8* pColors=aRes.getArray(); for( std::size_t i=0; iAlpha); *pColors++ = vcl::unotools::toByteColor(fAlpha*pIn->Blue); *pColors++ = vcl::unotools::toByteColor(fAlpha*pIn->Green); *pColors++ = vcl::unotools::toByteColor(fAlpha*pIn->Red); *pColors++ = vcl::unotools::toByteColor(fAlpha); ++pIn; } return aRes; } virtual uno::Sequence< ::sal_Int8 > SAL_CALL convertIntegerFromPARGB( const uno::Sequence< rendering::ARGBColor >& rgbColor ) override { const rendering::ARGBColor* pIn( rgbColor.getConstArray() ); const std::size_t nLen( rgbColor.getLength() ); uno::Sequence< sal_Int8 > aRes(nLen*4); sal_Int8* pColors=aRes.getArray(); for( std::size_t i=0; iBlue); *pColors++ = vcl::unotools::toByteColor(pIn->Green); *pColors++ = vcl::unotools::toByteColor(pIn->Red); *pColors++ = vcl::unotools::toByteColor(pIn->Alpha); ++pIn; } return aRes; } public: CairoColorSpace() : maComponentTags(4), maBitCounts(4) { sal_Int8* pTags = maComponentTags.getArray(); sal_Int32* pBitCounts = maBitCounts.getArray(); pTags[0] = rendering::ColorComponentTag::RGB_BLUE; pTags[1] = rendering::ColorComponentTag::RGB_GREEN; pTags[2] = rendering::ColorComponentTag::RGB_RED; pTags[3] = rendering::ColorComponentTag::PREMULTIPLIED_ALPHA; pBitCounts[0] = pBitCounts[1] = pBitCounts[2] = pBitCounts[3] = 8; } }; class CairoNoAlphaColorSpace : 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 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(this), 0); uno::Sequence< rendering::RGBColor > aRes(nLen/4); rendering::RGBColor* pOut( aRes.getArray() ); for( std::size_t i=0; i impl_convertToARGB( const uno::Sequence< double >& deviceColor ) { 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(this), 0); uno::Sequence< rendering::ARGBColor > aRes(nLen/4); rendering::ARGBColor* pOut( aRes.getArray() ); for( std::size_t i=0; i SAL_CALL convertToARGB( const uno::Sequence< double >& deviceColor ) override { return impl_convertToARGB( deviceColor ); } virtual uno::Sequence< rendering::ARGBColor > SAL_CALL convertToPARGB( const uno::Sequence< double >& deviceColor ) override { return impl_convertToARGB( deviceColor ); } virtual uno::Sequence< double > SAL_CALL convertFromRGB( const uno::Sequence< rendering::RGBColor >& rgbColor ) override { const rendering::RGBColor* pIn( rgbColor.getConstArray() ); const std::size_t nLen( rgbColor.getLength() ); uno::Sequence< double > aRes(nLen*4); double* pColors=aRes.getArray(); for( std::size_t i=0; iBlue; *pColors++ = pIn->Green; *pColors++ = pIn->Red; *pColors++ = 1.0; // the value does not matter ++pIn; } return aRes; } uno::Sequence< double > impl_convertFromARGB( const uno::Sequence< rendering::ARGBColor >& rgbColor ) { const rendering::ARGBColor* pIn( rgbColor.getConstArray() ); const std::size_t nLen( rgbColor.getLength() ); uno::Sequence< double > aRes(nLen*4); double* pColors=aRes.getArray(); for( std::size_t i=0; iBlue; *pColors++ = pIn->Green; *pColors++ = pIn->Red; *pColors++ = 1.0; // the value does not matter ++pIn; } return aRes; } virtual uno::Sequence< double > SAL_CALL convertFromARGB( const uno::Sequence< rendering::ARGBColor >& rgbColor ) override { return impl_convertFromARGB( rgbColor ); } virtual uno::Sequence< double > SAL_CALL convertFromPARGB( const uno::Sequence< rendering::ARGBColor >& rgbColor ) override { return impl_convertFromARGB( rgbColor ); } // XIntegerBitmapColorSpace virtual ::sal_Int32 SAL_CALL getBitsPerPixel( ) override { return 32; } virtual uno::Sequence< ::sal_Int32 > SAL_CALL getComponentBitCounts( ) override { return maBitCounts; } virtual ::sal_Int8 SAL_CALL getEndianness( ) override { return util::Endianness::LITTLE; } virtual uno::Sequence SAL_CALL convertFromIntegerColorSpace( const uno::Sequence< ::sal_Int8 >& deviceColor, const uno::Reference< rendering::XColorSpace >& targetColorSpace ) override { if( dynamic_cast(targetColorSpace.get()) ) { const sal_Int8* 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(this), 0); uno::Sequence aRes(nLen); double* pOut( aRes.getArray() ); for( std::size_t i=0; i aIntermediate( convertIntegerToARGB(deviceColor)); return targetColorSpace->convertFromARGB(aIntermediate); } } virtual uno::Sequence< ::sal_Int8 > SAL_CALL convertToIntegerColorSpace( const uno::Sequence< ::sal_Int8 >& deviceColor, const uno::Reference< rendering::XIntegerBitmapColorSpace >& targetColorSpace ) override { if( dynamic_cast(targetColorSpace.get()) ) { // it's us, so simply pass-through the data return deviceColor; } else { // TODO(P3): if we know anything about target // colorspace, this can be greatly sped up uno::Sequence aIntermediate( convertIntegerToARGB(deviceColor)); return targetColorSpace->convertIntegerFromARGB(aIntermediate); } } virtual uno::Sequence< rendering::RGBColor > SAL_CALL convertIntegerToRGB( const uno::Sequence< ::sal_Int8 >& deviceColor ) override { const sal_Int8* 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(this), 0); uno::Sequence< rendering::RGBColor > aRes(nLen/4); rendering::RGBColor* pOut( aRes.getArray() ); for( std::size_t i=0; i SAL_CALL convertIntegerToARGB( const uno::Sequence< ::sal_Int8 >& deviceColor ) override { return impl_convertIntegerToARGB( deviceColor ); } virtual uno::Sequence< rendering::ARGBColor > SAL_CALL convertIntegerToPARGB( const uno::Sequence< ::sal_Int8 >& deviceColor ) override { return impl_convertIntegerToARGB( deviceColor ); } uno::Sequence< rendering::ARGBColor > impl_convertIntegerToARGB( const uno::Sequence< ::sal_Int8 >& deviceColor ) { const sal_Int8* 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(this), 0); uno::Sequence< rendering::ARGBColor > aRes(nLen/4); rendering::ARGBColor* pOut( aRes.getArray() ); for( std::size_t i=0; i SAL_CALL convertIntegerFromRGB( const uno::Sequence< rendering::RGBColor >& rgbColor ) override { const rendering::RGBColor* pIn( rgbColor.getConstArray() ); const std::size_t nLen( rgbColor.getLength() ); uno::Sequence< sal_Int8 > aRes(nLen*4); sal_Int8* pColors=aRes.getArray(); for( std::size_t i=0; iBlue); *pColors++ = vcl::unotools::toByteColor(pIn->Green); *pColors++ = vcl::unotools::toByteColor(pIn->Red); *pColors++ = -1; // the value does not matter ++pIn; } return aRes; } virtual uno::Sequence< ::sal_Int8 > SAL_CALL convertIntegerFromARGB( const uno::Sequence< rendering::ARGBColor >& rgbColor ) override { return impl_convertIntegerFromARGB( rgbColor ); } virtual uno::Sequence< ::sal_Int8 > SAL_CALL convertIntegerFromPARGB( const uno::Sequence< rendering::ARGBColor >& rgbColor ) override { return impl_convertIntegerFromARGB( rgbColor ); } uno::Sequence< ::sal_Int8 > impl_convertIntegerFromARGB( const uno::Sequence< rendering::ARGBColor >& rgbColor ) { const rendering::ARGBColor* pIn( rgbColor.getConstArray() ); const std::size_t nLen( rgbColor.getLength() ); uno::Sequence< sal_Int8 > aRes(nLen*4); sal_Int8* pColors=aRes.getArray(); for( std::size_t i=0; iBlue); *pColors++ = vcl::unotools::toByteColor(pIn->Green); *pColors++ = vcl::unotools::toByteColor(pIn->Red); *pColors++ = -1; // the value does not matter ++pIn; } return aRes; } public: CairoNoAlphaColorSpace() : maComponentTags(3), maBitCounts(3) { sal_Int8* pTags = maComponentTags.getArray(); sal_Int32* pBitCounts = maBitCounts.getArray(); pTags[0] = rendering::ColorComponentTag::RGB_BLUE; pTags[1] = rendering::ColorComponentTag::RGB_GREEN; pTags[2] = rendering::ColorComponentTag::RGB_RED; pBitCounts[0] = pBitCounts[1] = pBitCounts[2] = 8; } }; uno::Reference& GetCairoNoAlphaColorSpace() { static uno::Reference SPACE = new CairoNoAlphaColorSpace(); return SPACE; }; uno::Reference& GetCairoColorSpace() { static uno::Reference SPACE = new CairoColorSpace(); return SPACE; }; } rendering::IntegerBitmapLayout CanvasHelper::getMemoryLayout() { if( !mpCairo ) return rendering::IntegerBitmapLayout(); // we're disposed const geometry::IntegerSize2D aSize(getSize()); return impl_getMemoryLayout( aSize.Width, aSize.Height ); } rendering::IntegerBitmapLayout CanvasHelper::impl_getMemoryLayout( const sal_Int32 nWidth, const sal_Int32 nHeight ) { rendering::IntegerBitmapLayout aLayout; aLayout.ScanLines = nHeight; aLayout.ScanLineBytes = nWidth*4; aLayout.ScanLineStride = aLayout.ScanLineBytes; aLayout.PlaneStride = 0; aLayout.ColorSpace = mbHaveAlpha ? GetCairoColorSpace() : GetCairoNoAlphaColorSpace(); aLayout.Palette.clear(); aLayout.IsMsbFirst = false; return aLayout; } bool CanvasHelper::repaint( const SurfaceSharedPtr& pSurface, const rendering::ViewState& viewState, const rendering::RenderState& renderState ) { SAL_INFO( "canvas.cairo", "CanvasHelper::repaint"); if( !mpCairo ) return true; 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 ); aMatrix.xx = aMatrix.yy = 1; cairo_set_matrix( mpCairo.get(), &aMatrix ); cairo_set_source_surface( mpCairo.get(), pSurface->getCairoSurface().get(), 0, 0 ); cairo_paint( mpCairo.get() ); cairo_restore( mpCairo.get() ); return true; } } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */