/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /************************************************************************* * * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * Copyright 2000, 2010 Oracle and/or its affiliates. * * OpenOffice.org - a multi-platform office productivity suite * * This file is part of OpenOffice.org. * * OpenOffice.org is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License version 3 * only, as published by the Free Software Foundation. * * OpenOffice.org is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License version 3 for more details * (a copy is included in the LICENSE file that accompanied this code). * * You should have received a copy of the GNU Lesser General Public License * version 3 along with OpenOffice.org. If not, see * * for a copy of the LGPLv3 License. * ************************************************************************/ #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 #include "cairo_spritecanvas.hxx" #include "cairo_cachedbitmap.hxx" #include "cairo_canvashelper.hxx" #include "cairo_canvasbitmap.hxx" #include #include using namespace ::cairo; using namespace ::com::sun::star; namespace cairocanvas { CanvasHelper::CanvasHelper() : mpSurfaceProvider(NULL), mpDevice(NULL), mpVirtualDevice(), mbHaveAlpha(), mpCairo(), mpSurface(), maSize() { } void CanvasHelper::disposing() { mpSurface.reset(); mpCairo.reset(); mpVirtualDevice.reset(); mpDevice = NULL; mpSurfaceProvider = NULL; } 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.reset(); mpSurface = pSurface; mpCairo = pSurface->getCairo(); } static void setColor( Cairo* 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 ) { Matrix aViewMatrix; Matrix aRenderMatrix; Matrix 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() ) { OSL_TRACE ("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() ) { OSL_TRACE ("render clip BEGIN"); doPolyPolygonPath( renderState.Clip, Clip ); OSL_TRACE ("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() { OSL_TRACE ("clear whole area: %d x %d", maSize.getX(), maSize.getY() ); if( mpCairo ) { 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.getX(), maSize.getY() ); cairo_fill( mpCairo.get() ); cairo_restore( mpCairo.get() ); } } void CanvasHelper::drawPoint( const rendering::XCanvas* , const geometry::RealPoint2D& , const rendering::ViewState& , const rendering::RenderState& ) { } 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 ) { 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 ) { 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 ); 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() ); } } #define CANVASBITMAP_IMPLEMENTATION_NAME "CairoCanvas::CanvasBitmap" #define PARAMETRICPOLYPOLYGON_IMPLEMENTATION_NAME "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 ) return aBmpEx; // TODO(F1): extract pixel from XBitmap interface ENSURE_OR_THROW( false, "bitmapExFromXBitmap(): could not extract BitmapEx" ); return ::BitmapEx(); } static bool readAlpha( BitmapReadAccess* pAlphaReadAcc, long nY, const long nWidth, unsigned char* data, long nOff ) { bool bIsAlpha = false; long nX; int nAlpha; Scanline pReadScan; nOff += 3; switch( pAlphaReadAcc->GetScanlineFormat() ) { case BMP_FORMAT_8BIT_TC_MASK: pReadScan = pAlphaReadAcc->GetScanline( nY ); for( nX = 0; nX < nWidth; nX++ ) { nAlpha = data[ nOff ] = 255 - ( *pReadScan++ ); if( nAlpha != 255 ) bIsAlpha = true; nOff += 4; } break; case BMP_FORMAT_8BIT_PAL: pReadScan = pAlphaReadAcc->GetScanline( nY ); for( nX = 0; nX < nWidth; nX++ ) { nAlpha = data[ nOff ] = 255 - ( pAlphaReadAcc->GetPaletteColor( *pReadScan++ ).GetIndex() ); if( nAlpha != 255 ) bIsAlpha = true; nOff += 4; } break; default: OSL_TRACE( "fallback to GetColor for alpha - slow, format: %d", pAlphaReadAcc->GetScanlineFormat() ); for( nX = 0; nX < nWidth; nX++ ) { nAlpha = data[ nOff ] = 255 - pAlphaReadAcc->GetColor( nY, nX ).GetIndex(); if( nAlpha != 255 ) bIsAlpha = true; nOff += 4; } } return bIsAlpha; } /** 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 = NULL; 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.IsTransparent() && !aBmpEx.IsAlpha() ) { pSurface = rSurfaceProvider->createSurface( aBitmap ); data = NULL; bHasAlpha = false; } if( !pSurface ) { AlphaMask aAlpha = aBmpEx.GetAlpha(); ::BitmapReadAccess* pBitmapReadAcc = aBitmap.AcquireReadAccess(); ::BitmapReadAccess* pAlphaReadAcc = NULL; const long nWidth = pBitmapReadAcc->Width(); const long nHeight = pBitmapReadAcc->Height(); long nX, nY; bool bIsAlpha = false; if( aBmpEx.IsTransparent() || aBmpEx.IsAlpha() ) pAlphaReadAcc = aAlpha.AcquireReadAccess(); data = (unsigned char*) malloc( nWidth*nHeight*4 ); long nOff = 0; ::Color aColor; unsigned int nAlpha = 255; for( nY = 0; nY < nHeight; nY++ ) { ::Scanline pReadScan; switch( pBitmapReadAcc->GetScanlineFormat() ) { case BMP_FORMAT_8BIT_PAL: pReadScan = pBitmapReadAcc->GetScanline( nY ); if( pAlphaReadAcc ) if( readAlpha( pAlphaReadAcc, nY, nWidth, data, nOff ) ) bIsAlpha = true; for( nX = 0; nX < nWidth; nX++ ) { #ifdef OSL_BIGENDIAN if( pAlphaReadAcc ) nAlpha = data[ nOff++ ]; else nAlpha = data[ nOff++ ] = 255; #else if( pAlphaReadAcc ) nAlpha = data[ nOff + 3 ]; else nAlpha = data[ nOff + 3 ] = 255; #endif aColor = pBitmapReadAcc->GetPaletteColor( *pReadScan++ ); #ifdef OSL_BIGENDIAN data[ nOff++ ] = sal::static_int_cast(( nAlpha*( aColor.GetRed() ) )/255 ); data[ nOff++ ] = sal::static_int_cast(( nAlpha*( aColor.GetGreen() ) )/255 ); data[ nOff++ ] = sal::static_int_cast(( nAlpha*( aColor.GetBlue() ) )/255 ); #else data[ nOff++ ] = sal::static_int_cast(( nAlpha*( aColor.GetBlue() ) )/255 ); data[ nOff++ ] = sal::static_int_cast(( nAlpha*( aColor.GetGreen() ) )/255 ); data[ nOff++ ] = sal::static_int_cast(( nAlpha*( aColor.GetRed() ) )/255 ); nOff++; #endif } break; case BMP_FORMAT_24BIT_TC_BGR: pReadScan = pBitmapReadAcc->GetScanline( nY ); if( pAlphaReadAcc ) if( readAlpha( pAlphaReadAcc, nY, nWidth, data, nOff ) ) bIsAlpha = true; for( nX = 0; nX < nWidth; nX++ ) { #ifdef OSL_BIGENDIAN if( pAlphaReadAcc ) nAlpha = data[ nOff ]; else nAlpha = data[ nOff ] = 255; data[ nOff + 3 ] = sal::static_int_cast(( nAlpha*( *pReadScan++ ) )/255 ); data[ nOff + 2 ] = sal::static_int_cast(( nAlpha*( *pReadScan++ ) )/255 ); data[ nOff + 1 ] = sal::static_int_cast(( nAlpha*( *pReadScan++ ) )/255 ); nOff += 4; #else if( pAlphaReadAcc ) nAlpha = data[ nOff + 3 ]; else nAlpha = data[ nOff + 3 ] = 255; data[ nOff++ ] = sal::static_int_cast(( nAlpha*( *pReadScan++ ) )/255 ); data[ nOff++ ] = sal::static_int_cast(( nAlpha*( *pReadScan++ ) )/255 ); data[ nOff++ ] = sal::static_int_cast(( nAlpha*( *pReadScan++ ) )/255 ); nOff++; #endif } break; case BMP_FORMAT_24BIT_TC_RGB: pReadScan = pBitmapReadAcc->GetScanline( nY ); if( pAlphaReadAcc ) if( readAlpha( pAlphaReadAcc, nY, nWidth, data, nOff ) ) bIsAlpha = true; for( nX = 0; nX < nWidth; nX++ ) { #ifdef OSL_BIGENDIAN if( pAlphaReadAcc ) nAlpha = data[ nOff++ ]; else nAlpha = data[ nOff++ ] = 255; data[ nOff++ ] = sal::static_int_cast(( nAlpha*( *pReadScan++ ) )/255 ); data[ nOff++ ] = sal::static_int_cast(( nAlpha*( *pReadScan++ ) )/255 ); data[ nOff++ ] = sal::static_int_cast(( nAlpha*( *pReadScan++ ) )/255 ); #else if( pAlphaReadAcc ) nAlpha = data[ nOff + 3 ]; else nAlpha = data[ nOff + 3 ] = 255; data[ nOff++ ] = sal::static_int_cast(( nAlpha*( pReadScan[ 2 ] ) )/255 ); data[ nOff++ ] = sal::static_int_cast(( nAlpha*( pReadScan[ 1 ] ) )/255 ); data[ nOff++ ] = sal::static_int_cast(( nAlpha*( pReadScan[ 0 ] ) )/255 ); pReadScan += 3; nOff++; #endif } break; case BMP_FORMAT_32BIT_TC_BGRA: pReadScan = pBitmapReadAcc->GetScanline( nY ); if( pAlphaReadAcc ) if( readAlpha( pAlphaReadAcc, nY, nWidth, data, nOff ) ) bIsAlpha = true; for( nX = 0; nX < nWidth; nX++ ) { #ifdef OSL_BIGENDIAN if( pAlphaReadAcc ) nAlpha = data[ nOff++ ]; else nAlpha = data[ nOff++ ] = 255; data[ nOff++ ] = sal::static_int_cast(( nAlpha*( pReadScan[ 2 ] ) )/255 ); data[ nOff++ ] = sal::static_int_cast(( nAlpha*( pReadScan[ 1 ] ) )/255 ); data[ nOff++ ] = sal::static_int_cast(( nAlpha*( pReadScan[ 0 ] ) )/255 ); pReadScan += 4; #else if( pAlphaReadAcc ) nAlpha = data[ nOff + 3 ]; else nAlpha = data[ nOff + 3 ] = 255; data[ nOff++ ] = sal::static_int_cast(( nAlpha*( *pReadScan++ ) )/255 ); data[ nOff++ ] = sal::static_int_cast(( nAlpha*( *pReadScan++ ) )/255 ); data[ nOff++ ] = sal::static_int_cast(( nAlpha*( *pReadScan++ ) )/255 ); pReadScan++; nOff++; #endif } break; case BMP_FORMAT_32BIT_TC_RGBA: pReadScan = pBitmapReadAcc->GetScanline( nY ); if( pAlphaReadAcc ) if( readAlpha( pAlphaReadAcc, nY, nWidth, data, nOff ) ) bIsAlpha = true; for( nX = 0; nX < nWidth; nX++ ) { #ifdef OSL_BIGENDIAN if( pAlphaReadAcc ) nAlpha = data[ nOff ++ ]; else nAlpha = data[ nOff ++ ] = 255; data[ nOff++ ] = sal::static_int_cast(( nAlpha*( *pReadScan++ ) )/255 ); data[ nOff++ ] = sal::static_int_cast(( nAlpha*( *pReadScan++ ) )/255 ); data[ nOff++ ] = sal::static_int_cast(( nAlpha*( *pReadScan++ ) )/255 ); pReadScan++; #else if( pAlphaReadAcc ) nAlpha = data[ nOff + 3 ]; else nAlpha = data[ nOff + 3 ] = 255; data[ nOff++ ] = sal::static_int_cast(( nAlpha*( pReadScan[ 2 ] ) )/255 ); data[ nOff++ ] = sal::static_int_cast(( nAlpha*( pReadScan[ 1 ] ) )/255 ); data[ nOff++ ] = sal::static_int_cast(( nAlpha*( pReadScan[ 0 ] ) )/255 ); pReadScan += 4; nOff++; #endif } break; default: OSL_TRACE( "fallback to GetColor - slow, format: %d", pBitmapReadAcc->GetScanlineFormat() ); if( pAlphaReadAcc ) if( readAlpha( pAlphaReadAcc, nY, nWidth, data, nOff ) ) bIsAlpha = true; for( nX = 0; nX < nWidth; nX++ ) { aColor = pBitmapReadAcc->GetColor( nY, nX ); // cairo need premultiplied color values // TODO(rodo) handle endianess #ifdef OSL_BIGENDIAN if( pAlphaReadAcc ) nAlpha = data[ nOff++ ]; else nAlpha = data[ nOff++ ] = 255; data[ nOff++ ] = sal::static_int_cast(( nAlpha*aColor.GetRed() )/255 ); data[ nOff++ ] = sal::static_int_cast(( nAlpha*aColor.GetGreen() )/255 ); data[ nOff++ ] = sal::static_int_cast(( nAlpha*aColor.GetBlue() )/255 ); #else if( pAlphaReadAcc ) nAlpha = data[ nOff + 3 ]; else nAlpha = data[ nOff + 3 ] = 255; data[ nOff++ ] = sal::static_int_cast(( nAlpha*aColor.GetBlue() )/255 ); data[ nOff++ ] = sal::static_int_cast(( nAlpha*aColor.GetGreen() )/255 ); data[ nOff++ ] = sal::static_int_cast(( nAlpha*aColor.GetRed() )/255 ); nOff ++; #endif } } } aBitmap.ReleaseAccess( pBitmapReadAcc ); if( pAlphaReadAcc ) aAlpha.ReleaseAccess( pAlphaReadAcc ); SurfaceSharedPtr pImageSurface = createSurface( CairoSurfaceSharedPtr( cairo_image_surface_create_for_data( data, bIsAlpha ? CAIRO_FORMAT_ARGB32 : CAIRO_FORMAT_RGB24, nWidth, nHeight, nWidth*4 ), &cairo_surface_destroy) ); pSurface = pImageSurface; bHasAlpha = bIsAlpha; OSL_TRACE("image: %d x %d alpha: %d alphaRead %p", nWidth, nHeight, bIsAlpha, pAlphaReadAcc); } } return pSurface; } static void addColorStops( Pattern* pPattern, const uno::Sequence< uno::Sequence< double > >& rColors, const uno::Sequence< double >& rStops, bool bReverseStops = false ) { float stop; int i; OSL_ASSERT( rColors.getLength() == rStops.getLength() ); for( i = 0; i < rColors.getLength(); i++ ) { const uno::Sequence< double >& rColor( rColors[i] ); 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 ) { uno::Sequence aRes(3); aRes[0] = basegfx::tools::lerp(rLeft[0],rRight[0],fAlpha); aRes[1] = basegfx::tools::lerp(rLeft[1],rRight[1],fAlpha); aRes[2] = basegfx::tools::lerp(rLeft[2],rRight[2],fAlpha); return aRes; } else if( rLeft.getLength() == 4 ) { uno::Sequence aRes(4); aRes[0] = basegfx::tools::lerp(rLeft[0],rRight[0],fAlpha); aRes[1] = basegfx::tools::lerp(rLeft[1],rRight[1],fAlpha); aRes[2] = basegfx::tools::lerp(rLeft[2],rRight[2],fAlpha); aRes[3] = basegfx::tools::lerp(rLeft[3],rRight[3],fAlpha); return aRes; } return uno::Sequence(); } static Pattern* patternFromParametricPolyPolygon( ::canvas::ParametricPolyPolygon& rPolygon ) { Pattern* pPattern = NULL; const ::canvas::ParametricPolyPolygon::Values aValues = rPolygon.getValues(); double x0, x1, y0, y1, cx, cy, r0, r1; switch( aValues.meType ) { case ::canvas::ParametricPolyPolygon::GRADIENT_LINEAR: x0 = 0; y0 = 0; x1 = 1; y1 = 0; pPattern = cairo_pattern_create_linear( x0, y0, x1, y1 ); addColorStops( pPattern, aValues.maColors, aValues.maStops ); break; case ::canvas::ParametricPolyPolygon::GRADIENT_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* pCairo, const uno::Sequence< rendering::Texture >* pTextures, const SurfaceProviderRef& pDevice, const basegfx::B2DRange& rBounds ) { switch( aOperation ) { case Fill: /* TODO: multitexturing */ if( pTextures ) { const ::com::sun::star::rendering::Texture& aTexture ( (*pTextures)[0] ); if( aTexture.Bitmap.is() ) { unsigned char* data = NULL; bool bHasAlpha = false; SurfaceSharedPtr pSurface = surfaceFromXBitmap( (*pTextures)[0].Bitmap, pDevice, data, bHasAlpha ); if( pSurface ) { cairo_pattern_t* pPattern; cairo_save( pCairo ); ::com::sun::star::geometry::AffineMatrix2D aTransform( aTexture.AffineTransform ); Matrix 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, &aTextureMatrix, &aScaleMatrix ); 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 ) { #if CAIRO_VERSION >= 10200 cairo_pattern_set_extend( pPattern, CAIRO_EXTEND_PAD ); #else #warning "fallback for cairo before version 1.2" cairo_pattern_set_extend( pPattern, CAIRO_EXTEND_NONE ); #endif } aScaledTextureMatrix.x0 = basegfx::fround( aScaledTextureMatrix.x0 ); aScaledTextureMatrix.y0 = basegfx::fround( aScaledTextureMatrix.y0 ); 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 ); OSL_TRACE( "gradient fill" ); if( xRef.is() && xRef->getImplementationName() == PARAMETRICPOLYPOLYGON_IMPLEMENTATION_NAME ) { // TODO(Q1): Maybe use dynamic_cast here // TODO(E1): Return value // TODO(F1): FillRule OSL_TRACE( "known implementation" ); ::canvas::ParametricPolyPolygon* pPolyImpl = static_cast< ::canvas::ParametricPolyPolygon* >( aTexture.Gradient.get() ); ::com::sun::star::geometry::AffineMatrix2D aTransform( aTexture.AffineTransform ); Matrix aTextureMatrix; cairo_matrix_init( &aTextureMatrix, aTransform.m00, aTransform.m10, aTransform.m01, aTransform.m11, aTransform.m02, aTransform.m12); if( pPolyImpl->getValues().meType == canvas::ParametricPolyPolygon::GRADIENT_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::tools::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->getLength(), "CanvasHelper::fillTexturedPolyPolygon: empty texture sequence"); bool bOpToDo = false; Matrix aOrigMatrix, aIdentityMatrix; double nX, nY, nBX, nBY, nAX, nAY; 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++ ) { ::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 + aPolygon.isClosed()*aPolygon.areControlPointsUsed() ); if( nPointCount > 1) { bool bIsBezier = aPolygon.areControlPointsUsed(); bool bIsRectangle = ::basegfx::tools::isRectangle( aPolygon ); ::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 && (bIsRectangle || 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 ); OSL_TRACE( "move to %f,%f", 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; } cairo_curve_to( pCairo, nAX, nAY, nBX, nBY, nX, nY ); } else { cairo_line_to( pCairo, nX, nY ); OSL_TRACE( "line to %f,%f", nX, nY ); } bOpToDo = true; } } if( aPolygon.isClosed() ) cairo_close_path( pCairo ); } else { OSL_TRACE( "empty polygon for op: %d", 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, Cairo* pCairo ) const { const ::basegfx::B2DPolyPolygon& rPolyPoly( ::basegfx::unotools::b2DPolyPolygonFromXPolyPolygon2D(xPolyPolygon) ); if( !pCairo ) pCairo = mpCairo.get(); if(bNoLineJoin && Stroke == aOperation) { // 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 OSL_TRACE ("CanvasHelper called after it was disposed"); #ifdef CAIRO_CANVAS_PERF_TRACE mxDevice->stopPerfTrace( &aTimer, "drawPolyPolygon" ); #endif return uno::Reference< rendering::XCachedPrimitive >(NULL); } 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 ); Matrix aMatrix; double w = strokeAttributes.StrokeWidth, h = 0; cairo_get_matrix( mpCairo.get(), &aMatrix ); cairo_matrix_transform_distance( &aMatrix, &w, &h ); cairo_set_line_width( mpCairo.get(), w ); 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 ) { // cairo doesn't have join type NONE so we use MITER as it's pretty close case rendering::PathJoinType::NONE: bNoLineJoin = true; 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; } if( strokeAttributes.DashArray.getLength() > 0 ) { double* pDashArray = new double[ strokeAttributes.DashArray.getLength() ]; for( sal_Int32 i=0; istopPerfTrace( &aTimer, "strokePolyPolygon" ); #endif // TODO(P1): Provide caching here. return uno::Reference< rendering::XCachedPrimitive >(NULL); } 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 >(NULL); } 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 >(NULL); } 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 >(NULL); } 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 OSL_TRACE ("CanvasHelper called after it was disposed"); #ifdef CAIRO_CANVAS_PERF_TRACE mxDevice->stopPerfTrace( &aTimer, "fillPolyPolygon" ); #endif return uno::Reference< rendering::XCachedPrimitive >(NULL); } 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 >(NULL); } 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 >(NULL); } 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 = uno::Reference< rendering::XCachedPrimitive >(NULL); geometry::IntegerSize2D aBitmapSize = rSize; if( mpCairo ) { cairo_save( mpCairo.get() ); cairo_rectangle( mpCairo.get(), 0, 0, maSize.getX(), maSize.getY() ); cairo_clip( mpCairo.get() ); useStates( viewState, renderState, true ); Matrix 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 = uno::Reference< rendering::XCachedPrimitive >( 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 OSL_TRACE ("chance to change surface to rgb, %f, %f, %f x %f (%d x %d)", x, y, width, height, maSize.getX(), maSize.getY() ); if( x <= 0 && y <= 0 && x + width >= maSize.getX() && y + height >= maSize.getY() ) { OSL_TRACE ("trying to change surface to rgb"); if( mpSurfaceProvider ) { SurfaceSharedPtr pNewSurface = mpSurfaceProvider->changeSurface( false, false ); 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 ); #if CAIRO_VERSION >= 10200 cairo_pattern_set_extend( cairo_get_source(mpCairo.get()), CAIRO_EXTEND_PAD ); #endif cairo_rectangle( mpCairo.get(), 0, 0, aBitmapSize.Width, aBitmapSize.Height ); cairo_clip( mpCairo.get() ); if( bModulateColors ) cairo_paint_with_alpha( mpCairo.get(), renderState.DeviceColor[3] ); else cairo_paint( mpCairo.get() ); cairo_restore( mpCairo.get() ); } else OSL_TRACE ("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 = NULL; 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 = uno::Reference< rendering::XCachedPrimitive >(NULL); #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 = NULL; 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 = uno::Reference< rendering::XCachedPrimitive >(NULL); #ifdef CAIRO_CANVAS_PERF_TRACE mxDevice->stopPerfTrace( &aTimer, "drawBitmap" ); #endif return rv; } uno::Reference< rendering::XGraphicDevice > CanvasHelper::getDevice() { return uno::Reference< rendering::XGraphicDevice >(mpDevice); } void CanvasHelper::copyRect( const rendering::XCanvas* , const uno::Reference< rendering::XBitmapCanvas >& /*sourceCanvas*/, const geometry::RealRectangle2D& /*sourceRect*/, const rendering::ViewState& /*sourceViewState*/, const rendering::RenderState& /*sourceRenderState*/, const geometry::RealRectangle2D& /*destRect*/, const rendering::ViewState& /*destViewState*/, const rendering::RenderState& /*destRenderState*/ ) { // TODO(F2): copyRect NYI } geometry::IntegerSize2D CanvasHelper::getSize() { if( !mpSurfaceProvider ) geometry::IntegerSize2D(1, 1); // we're disposed return ::basegfx::unotools::integerSize2DFromB2ISize( maSize ); } uno::Reference< rendering::XBitmap > CanvasHelper::getScaledBitmap( const geometry::RealSize2D& newSize, sal_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 OSL_TRACE ("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 ) { aLayout = getMemoryLayout(); const sal_Int32 nWidth( rect.X2 - rect.X1 ); const sal_Int32 nHeight( rect.Y2 - rect.Y1 ); uno::Sequence< sal_Int8 > aRes( 4*nWidth*nHeight ); sal_Int8* pData = aRes.getArray(); cairo_surface_t* pImageSurface = cairo_image_surface_create_for_data( (unsigned char *) pData, CAIRO_FORMAT_ARGB32, 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.ScanLines = nHeight; aLayout.ScanLineBytes = nWidth*4; aLayout.ScanLineStride = aLayout.ScanLineBytes; return aRes; } return uno::Sequence< sal_Int8 >(); } void CanvasHelper::setData( const uno::Sequence< sal_Int8 >& /*data*/, const rendering::IntegerBitmapLayout& /*bitmapLayout*/, const geometry::IntegerRectangle2D& /*rect*/ ) { } void CanvasHelper::setPixel( const uno::Sequence< sal_Int8 >& /*color*/, const rendering::IntegerBitmapLayout& /*bitmapLayout*/, const geometry::IntegerPoint2D& /*pos*/ ) { } uno::Sequence< sal_Int8 > CanvasHelper::getPixel( rendering::IntegerBitmapLayout& /*bitmapLayout*/, const geometry::IntegerPoint2D& /*pos*/ ) { return uno::Sequence< sal_Int8 >(); } uno::Reference< rendering::XBitmapPalette > CanvasHelper::getPalette() { // TODO(F1): Palette bitmaps NYI return uno::Reference< rendering::XBitmapPalette >(); } namespace { class CairoColorSpace : public cppu::WeakImplHelper1< com::sun::star::rendering::XIntegerBitmapColorSpace > { private: uno::Sequence< sal_Int8 > maComponentTags; uno::Sequence< sal_Int32 > maBitCounts; virtual ::sal_Int8 SAL_CALL getType( ) throw (uno::RuntimeException) { return rendering::ColorSpaceType::RGB; } virtual uno::Sequence< ::sal_Int8 > SAL_CALL getComponentTags( ) throw (uno::RuntimeException) { return maComponentTags; } virtual ::sal_Int8 SAL_CALL getRenderingIntent( ) throw (uno::RuntimeException) { return rendering::RenderingIntent::PERCEPTUAL; } virtual uno::Sequence< beans::PropertyValue > SAL_CALL getProperties( ) throw (uno::RuntimeException) { return uno::Sequence< beans::PropertyValue >(); } virtual uno::Sequence< double > SAL_CALL convertColorSpace( const uno::Sequence< double >& deviceColor, const uno::Reference< rendering::XColorSpace >& targetColorSpace ) throw (lang::IllegalArgumentException, uno::RuntimeException) { // 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 ) throw (lang::IllegalArgumentException, uno::RuntimeException) { const double* pIn( deviceColor.getConstArray() ); const sal_Size 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( sal_Size i=0; i SAL_CALL convertToARGB( const uno::Sequence< double >& deviceColor ) throw (lang::IllegalArgumentException, uno::RuntimeException) { const double* pIn( deviceColor.getConstArray() ); const sal_Size 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( sal_Size i=0; i SAL_CALL convertToPARGB( const uno::Sequence< double >& deviceColor ) throw (lang::IllegalArgumentException, uno::RuntimeException) { const double* pIn( deviceColor.getConstArray() ); const sal_Size 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( sal_Size i=0; i SAL_CALL convertFromRGB( const uno::Sequence< rendering::RGBColor >& rgbColor ) throw (lang::IllegalArgumentException, uno::RuntimeException) { const rendering::RGBColor* pIn( rgbColor.getConstArray() ); const sal_Size nLen( rgbColor.getLength() ); uno::Sequence< double > aRes(nLen*4); double* pColors=aRes.getArray(); for( sal_Size 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 ) throw (lang::IllegalArgumentException, uno::RuntimeException) { const rendering::ARGBColor* pIn( rgbColor.getConstArray() ); const sal_Size nLen( rgbColor.getLength() ); uno::Sequence< double > aRes(nLen*4); double* pColors=aRes.getArray(); for( sal_Size 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 ) throw (lang::IllegalArgumentException, uno::RuntimeException) { const rendering::ARGBColor* pIn( rgbColor.getConstArray() ); const sal_Size nLen( rgbColor.getLength() ); uno::Sequence< double > aRes(nLen*4); double* pColors=aRes.getArray(); for( sal_Size i=0; iBlue; *pColors++ = pIn->Green; *pColors++ = pIn->Red; *pColors++ = pIn->Alpha; ++pIn; } return aRes; } // XIntegerBitmapColorSpace virtual ::sal_Int32 SAL_CALL getBitsPerPixel( ) throw (uno::RuntimeException) { return 32; } virtual uno::Sequence< ::sal_Int32 > SAL_CALL getComponentBitCounts( ) throw (uno::RuntimeException) { return maBitCounts; } virtual ::sal_Int8 SAL_CALL getEndianness( ) throw (uno::RuntimeException) { return util::Endianness::LITTLE; } virtual uno::Sequence SAL_CALL convertFromIntegerColorSpace( const uno::Sequence< ::sal_Int8 >& deviceColor, const uno::Reference< rendering::XColorSpace >& targetColorSpace ) throw (lang::IllegalArgumentException, uno::RuntimeException) { if( dynamic_cast(targetColorSpace.get()) ) { const sal_Int8* pIn( deviceColor.getConstArray() ); const sal_Size 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( sal_Size 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 ) throw (lang::IllegalArgumentException, uno::RuntimeException) { 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 ) throw (lang::IllegalArgumentException, uno::RuntimeException) { const sal_Int8* pIn( deviceColor.getConstArray() ); const sal_Size 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( sal_Size i=0; i SAL_CALL convertIntegerToARGB( const uno::Sequence< ::sal_Int8 >& deviceColor ) throw (lang::IllegalArgumentException, uno::RuntimeException) { const sal_Int8* pIn( deviceColor.getConstArray() ); const sal_Size 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( sal_Size i=0; i SAL_CALL convertIntegerToPARGB( const uno::Sequence< ::sal_Int8 >& deviceColor ) throw (lang::IllegalArgumentException, uno::RuntimeException) { const sal_Int8* pIn( deviceColor.getConstArray() ); const sal_Size 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( sal_Size i=0; i SAL_CALL convertIntegerFromRGB( const uno::Sequence< rendering::RGBColor >& rgbColor ) throw (lang::IllegalArgumentException, uno::RuntimeException) { const rendering::RGBColor* pIn( rgbColor.getConstArray() ); const sal_Size nLen( rgbColor.getLength() ); uno::Sequence< sal_Int8 > aRes(nLen*4); sal_Int8* pColors=aRes.getArray(); for( sal_Size 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 ) throw (lang::IllegalArgumentException, uno::RuntimeException) { const rendering::ARGBColor* pIn( rgbColor.getConstArray() ); const sal_Size nLen( rgbColor.getLength() ); uno::Sequence< sal_Int8 > aRes(nLen*4); sal_Int8* pColors=aRes.getArray(); for( sal_Size 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 ) throw (lang::IllegalArgumentException, uno::RuntimeException) { const rendering::ARGBColor* pIn( rgbColor.getConstArray() ); const sal_Size nLen( rgbColor.getLength() ); uno::Sequence< sal_Int8 > aRes(nLen*4); sal_Int8* pColors=aRes.getArray(); for( sal_Size 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; } }; struct CairoColorSpaceHolder : public rtl::StaticWithInit, CairoColorSpaceHolder> { uno::Reference operator()() { return new CairoColorSpace(); } }; } rendering::IntegerBitmapLayout CanvasHelper::getMemoryLayout() { if( !mpCairo ) return rendering::IntegerBitmapLayout(); // we're disposed const geometry::IntegerSize2D aSize(getSize()); rendering::IntegerBitmapLayout aLayout; aLayout.ScanLines = aSize.Height; aLayout.ScanLineBytes = aSize.Width*4; aLayout.ScanLineStride = aLayout.ScanLineBytes; aLayout.PlaneStride = 0; aLayout.ColorSpace = CairoColorSpaceHolder::get(); aLayout.Palette.clear(); aLayout.IsMsbFirst = sal_False; return aLayout; } void CanvasHelper::flush() const { } bool CanvasHelper::hasAlpha() const { return mbHaveAlpha; } bool CanvasHelper::repaint( const SurfaceSharedPtr& pSurface, const rendering::ViewState& viewState, const rendering::RenderState& renderState ) { OSL_TRACE("CanvasHelper::repaint"); if( mpCairo ) { cairo_save( mpCairo.get() ); cairo_rectangle( mpCairo.get(), 0, 0, maSize.getX(), maSize.getY() ); cairo_clip( mpCairo.get() ); useStates( viewState, renderState, true ); Matrix 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: */