/* -*- 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/. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ogl_canvasbitmap.hxx" #include "ogl_canvasfont.hxx" #include "ogl_canvastools.hxx" #include "ogl_texturecache.hxx" #include "ogl_tools.hxx" #include "ogl_canvashelper.hxx" using namespace ::com::sun::star; using namespace std::placeholders; namespace oglcanvas { /* Concepts: ========= This OpenGL canvas implementation tries to keep all render output as high-level as possible, i.e. geometry data and externally-provided bitmaps. Therefore, calls at the XCanvas-interfaces are not immediately transformed into colored pixel inside some GL buffer, but are retained simply with their call parameters. Only after XSpriteCanvas::updateScreen() has been called, this all gets transferred to the OpenGL subsystem and converted to a visible scene. The big advantage is, this makes sprite modifications practically zero-overhead, and saves a lot on texture memory (compared to the directx canvas, which immediately dumps every render call into a texture). The drawback, of course, is that complex images churn a lot of GPU cycles on every re-rendering. For the while, I'll be using immediate mode, i.e. transfer data over and over again to the OpenGL subsystem. Alternatively, there are display lists, which at least keep the data on the server, or even better, vertex buffers, which copy geometry data over en bloc. Next todo: put polygon geometry into vertex buffer (LRU cache necessary?) - or, rather, buffer objects! prune entries older than one updateScreen() call) Text: http://www.opengl.org/resources/features/fontsurvey/ */ struct CanvasHelper::Action { ::basegfx::B2DHomMatrix maTransform; GLenum meSrcBlendMode; GLenum meDstBlendMode; rendering::ARGBColor maARGBColor; ::basegfx::B2DPolyPolygonVector maPolyPolys; std::function< bool ( const CanvasHelper&, const ::basegfx::B2DHomMatrix&, GLenum, GLenum, const rendering::ARGBColor&, const ::basegfx::B2DPolyPolygonVector&)> maFunction; }; namespace { bool lcl_drawLine( const CanvasHelper& /*rHelper*/, const ::basegfx::B2DHomMatrix& rTransform, GLenum eSrcBlend, GLenum eDstBlend, const rendering::ARGBColor& rColor, const geometry::RealPoint2D& rStartPoint, const geometry::RealPoint2D& rEndPoint ) { TransformationPreserver aPreserver; setupState(rTransform, eSrcBlend, eDstBlend, rColor); glBegin(GL_LINES); glVertex2d(rStartPoint.X, rStartPoint.Y); glVertex2d(rEndPoint.X, rEndPoint.Y); glEnd(); return true; } bool lcl_drawPolyPolygon( const CanvasHelper& /*rHelper*/, const ::basegfx::B2DHomMatrix& rTransform, GLenum eSrcBlend, GLenum eDstBlend, const rendering::ARGBColor& rColor, const ::basegfx::B2DPolyPolygonVector& rPolyPolygons ) { TransformationPreserver aPreserver; setupState(rTransform, eSrcBlend, eDstBlend, rColor); for( const auto& rPoly : rPolyPolygons ) renderPolyPolygon( rPoly ); return true; } bool lcl_fillPolyPolygon( const CanvasHelper& /*rHelper*/, const ::basegfx::B2DHomMatrix& rTransform, GLenum eSrcBlend, GLenum eDstBlend, const rendering::ARGBColor& rColor, const ::basegfx::B2DPolyPolygonVector& rPolyPolygons ) { TransformationPreserver aPreserver; setupState(rTransform, eSrcBlend, eDstBlend, rColor); for( const auto& rPoly : rPolyPolygons ) { glBegin( GL_TRIANGLES ); renderComplexPolyPolygon( rPoly ); glEnd(); } return true; } bool lcl_fillGradientPolyPolygon( const CanvasHelper& rHelper, const ::basegfx::B2DHomMatrix& rTransform, GLenum eSrcBlend, GLenum eDstBlend, const ::canvas::ParametricPolyPolygon::Values& rValues, const rendering::Texture& rTexture, const ::basegfx::B2DPolyPolygonVector& rPolyPolygons ) { TransformationPreserver aPreserver; setupState(rTransform, eSrcBlend, eDstBlend, rendering::ARGBColor()); // convert to weird canvas textur coordinate system (not // [0,1]^2, but path coordinate system) ::basegfx::B2DHomMatrix aTextureTransform; ::basegfx::unotools::homMatrixFromAffineMatrix( aTextureTransform, rTexture.AffineTransform ); ::basegfx::B2DRange aBounds; for( const auto& rPoly : rPolyPolygons ) aBounds.expand( ::basegfx::utils::getRange( rPoly ) ); aTextureTransform.translate(-aBounds.getMinX(), -aBounds.getMinY()); aTextureTransform.scale(1/aBounds.getWidth(), 1/aBounds.getHeight()); const sal_Int32 nNumCols=rValues.maColors.getLength(); uno::Sequence< rendering::ARGBColor > aColors(nNumCols); rendering::ARGBColor* const pColors=aColors.getArray(); rendering::ARGBColor* pCurrCol=pColors; for( sal_Int32 i=0; igetDeviceColorSpace()->convertToARGB(rValues.maColors[i])[0]; OSL_ASSERT(nNumCols == rValues.maStops.getLength()); switch( rValues.meType ) { case ::canvas::ParametricPolyPolygon::GradientType::Linear: rHelper.getDeviceHelper()->useLinearGradientShader(pColors, rValues.maStops, aTextureTransform); break; case ::canvas::ParametricPolyPolygon::GradientType::Elliptical: rHelper.getDeviceHelper()->useRadialGradientShader(pColors, rValues.maStops, aTextureTransform); break; case ::canvas::ParametricPolyPolygon::GradientType::Rectangular: rHelper.getDeviceHelper()->useRectangularGradientShader(pColors, rValues.maStops, aTextureTransform); break; default: ENSURE_OR_THROW( false, "CanvasHelper lcl_fillGradientPolyPolygon(): Unexpected case" ); } for( const auto& rPoly : rPolyPolygons ) { glBegin(GL_TRIANGLES); renderComplexPolyPolygon( rPoly ); glEnd(); } glUseProgram(0); glLoadIdentity(); glMatrixMode(GL_MODELVIEW); return true; } bool lcl_drawOwnBitmap( const CanvasHelper& /*rHelper*/, const ::basegfx::B2DHomMatrix& rTransform, GLenum eSrcBlend, GLenum eDstBlend, const rendering::ARGBColor& rColor, const CanvasBitmap& rBitmap ) { TransformationPreserver aPreserver; setupState(rTransform, eSrcBlend, eDstBlend, rColor); return rBitmap.renderRecordedActions(); } bool lcl_drawGenericBitmap( const CanvasHelper& rHelper, const ::basegfx::B2DHomMatrix& rTransform, GLenum eSrcBlend, GLenum eDstBlend, const rendering::ARGBColor& rColor, const geometry::IntegerSize2D& rPixelSize, const uno::Sequence& rPixelData, sal_uInt32 nPixelCrc32 ) { TransformationPreserver aPreserver; setupState(rTransform, eSrcBlend, eDstBlend, rColor); const unsigned int nTexId=rHelper.getDeviceHelper()->getTextureCache().getTexture( rPixelSize, rPixelData.getConstArray(), nPixelCrc32); glBindTexture(GL_TEXTURE_2D, nTexId); glEnable(GL_TEXTURE_2D); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // blend against fixed vertex color; texture alpha is multiplied in glColor4f(1,1,1,1); glBegin(GL_TRIANGLE_STRIP); glTexCoord2f(0,0); glVertex2d(0,0); glTexCoord2f(0,1); glVertex2d(0, rPixelSize.Height); glTexCoord2f(1,0); glVertex2d(rPixelSize.Width,0); glTexCoord2f(1,1); glVertex2d(rPixelSize.Width,rPixelSize.Height); glEnd(); glBindTexture(GL_TEXTURE_2D, 0); glDisable(GL_TEXTURE_2D); return true; } bool lcl_fillTexturedPolyPolygon( const CanvasHelper& rHelper, const ::basegfx::B2DHomMatrix& rTransform, GLenum eSrcBlend, GLenum eDstBlend, const rendering::Texture& rTexture, const geometry::IntegerSize2D& rPixelSize, const uno::Sequence& rPixelData, sal_uInt32 nPixelCrc32, const ::basegfx::B2DPolyPolygonVector& rPolyPolygons ) { TransformationPreserver aPreserver; setupState(rTransform, eSrcBlend, eDstBlend, rendering::ARGBColor()); const unsigned int nTexId=rHelper.getDeviceHelper()->getTextureCache().getTexture( rPixelSize, rPixelData.getConstArray(), nPixelCrc32); glBindTexture(GL_TEXTURE_2D, nTexId); glEnable(GL_TEXTURE_2D); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // convert to weird canvas textur coordinate system (not // [0,1]^2, but path coordinate system) ::basegfx::B2DHomMatrix aTextureTransform; ::basegfx::unotools::homMatrixFromAffineMatrix( aTextureTransform, rTexture.AffineTransform ); ::basegfx::B2DRange aBounds; for( const auto& rPolyPolygon : rPolyPolygons ) aBounds.expand( ::basegfx::utils::getRange( rPolyPolygon ) ); aTextureTransform.translate(-aBounds.getMinX(), -aBounds.getMinY()); aTextureTransform.scale(1/aBounds.getWidth(), 1/aBounds.getHeight()); aTextureTransform.invert(); glMatrixMode(GL_TEXTURE); double aTexTransform[] = { aTextureTransform.get(0,0), aTextureTransform.get(1,0), 0, 0, aTextureTransform.get(0,1), aTextureTransform.get(1,1), 0, 0, 0, 0, 1, 0, aTextureTransform.get(0,2), aTextureTransform.get(1,2), 0, 1 }; glLoadMatrixd(aTexTransform); // blend against fixed vertex color; texture alpha is multiplied in glColor4f(1,1,1,rTexture.Alpha); for( const auto& rPolyPolygon : rPolyPolygons ) { glBegin(GL_TRIANGLES); renderComplexPolyPolygon( rPolyPolygon ); glEnd(); } glLoadIdentity(); glMatrixMode(GL_MODELVIEW); glBindTexture(GL_TEXTURE_2D, 0); glDisable(GL_TEXTURE_2D); return true; } } CanvasHelper::CanvasHelper() : mpDevice( nullptr ), mpDeviceHelper( nullptr ) {} CanvasHelper::~CanvasHelper() {} CanvasHelper& CanvasHelper::operator=( const CanvasHelper& rSrc ) { mpDevice = rSrc.mpDevice; mpDeviceHelper = rSrc.mpDeviceHelper; mpRecordedActions = rSrc.mpRecordedActions; return *this; } void CanvasHelper::disposing() { RecordVectorT aThrowaway; mpRecordedActions.swap( aThrowaway ); mpDevice = nullptr; mpDeviceHelper = nullptr; } void CanvasHelper::init( rendering::XGraphicDevice& rDevice, SpriteDeviceHelper& rDeviceHelper ) { mpDevice = &rDevice; mpDeviceHelper = &rDeviceHelper; } void CanvasHelper::clear() { mpRecordedActions->clear(); } void CanvasHelper::drawLine( const rendering::XCanvas* /*pCanvas*/, const geometry::RealPoint2D& aStartPoint, const geometry::RealPoint2D& aEndPoint, const rendering::ViewState& viewState, const rendering::RenderState& renderState ) { if( mpDevice ) { mpRecordedActions->push_back( Action() ); Action& rAct=mpRecordedActions->back(); setupGraphicsState( rAct, viewState, renderState ); rAct.maFunction = std::bind(&lcl_drawLine, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5, aStartPoint, aEndPoint); } } void CanvasHelper::drawBezier( const rendering::XCanvas* /*pCanvas*/, const geometry::RealBezierSegment2D& aBezierSegment, const geometry::RealPoint2D& aEndPoint, const rendering::ViewState& viewState, const rendering::RenderState& renderState ) { if( !mpDevice ) return; mpRecordedActions->push_back( Action() ); Action& rAct=mpRecordedActions->back(); setupGraphicsState( rAct, viewState, renderState ); // TODO(F2): subdivide&render whole curve rAct.maFunction = std::bind(&lcl_drawLine, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5, geometry::RealPoint2D( aBezierSegment.Px, aBezierSegment.Py), aEndPoint); } uno::Reference< rendering::XCachedPrimitive > CanvasHelper::drawPolyPolygon( const rendering::XCanvas* /*pCanvas*/, const uno::Reference< rendering::XPolyPolygon2D >& xPolyPolygon, const rendering::ViewState& viewState, const rendering::RenderState& renderState ) { ENSURE_OR_THROW( xPolyPolygon.is(), "CanvasHelper::drawPolyPolygon: polygon is NULL"); if( mpDevice ) { mpRecordedActions->push_back( Action() ); Action& rAct=mpRecordedActions->back(); setupGraphicsState( rAct, viewState, renderState ); rAct.maPolyPolys.push_back( ::basegfx::unotools::b2DPolyPolygonFromXPolyPolygon2D(xPolyPolygon)); rAct.maPolyPolys.back().makeUnique(); // own copy, for thread safety rAct.maFunction = &lcl_drawPolyPolygon; } // TODO(P1): Provide caching here. return uno::Reference< rendering::XCachedPrimitive >(nullptr); } uno::Reference< rendering::XCachedPrimitive > CanvasHelper::strokePolyPolygon( const rendering::XCanvas* /*pCanvas*/, const uno::Reference< rendering::XPolyPolygon2D >& xPolyPolygon, const rendering::ViewState& viewState, const rendering::RenderState& renderState, const rendering::StrokeAttributes& /*strokeAttributes*/ ) { ENSURE_OR_THROW( xPolyPolygon.is(), "CanvasHelper::strokePolyPolygon: polygon is NULL"); if( mpDevice ) { mpRecordedActions->push_back( Action() ); Action& rAct=mpRecordedActions->back(); setupGraphicsState( rAct, viewState, renderState ); rAct.maPolyPolys.push_back( ::basegfx::unotools::b2DPolyPolygonFromXPolyPolygon2D(xPolyPolygon)); rAct.maPolyPolys.back().makeUnique(); // own copy, for thread safety // TODO(F3): fallback to drawPolyPolygon currently rAct.maFunction = &lcl_drawPolyPolygon; } // TODO(P1): Provide caching here. return uno::Reference< rendering::XCachedPrimitive >(nullptr); } uno::Reference< rendering::XCachedPrimitive > CanvasHelper::strokeTexturedPolyPolygon( const rendering::XCanvas* /*pCanvas*/, 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* /*pCanvas*/, 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* /*pCanvas*/, 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* /*pCanvas*/, const uno::Reference< rendering::XPolyPolygon2D >& xPolyPolygon, const rendering::ViewState& viewState, const rendering::RenderState& renderState ) { ENSURE_OR_THROW( xPolyPolygon.is(), "CanvasHelper::fillPolyPolygon: polygon is NULL"); if( mpDevice ) { mpRecordedActions->push_back( Action() ); Action& rAct=mpRecordedActions->back(); setupGraphicsState( rAct, viewState, renderState ); rAct.maPolyPolys.push_back( ::basegfx::unotools::b2DPolyPolygonFromXPolyPolygon2D(xPolyPolygon)); rAct.maPolyPolys.back().makeUnique(); // own copy, for thread safety rAct.maFunction = &lcl_fillPolyPolygon; } // TODO(P1): Provide caching here. return uno::Reference< rendering::XCachedPrimitive >(nullptr); } uno::Reference< rendering::XCachedPrimitive > CanvasHelper::fillTexturedPolyPolygon( const rendering::XCanvas* /*pCanvas*/, const uno::Reference< rendering::XPolyPolygon2D >& xPolyPolygon, const rendering::ViewState& viewState, const rendering::RenderState& renderState, const uno::Sequence< rendering::Texture >& textures ) { ENSURE_OR_THROW( xPolyPolygon.is(), "CanvasHelper::fillPolyPolygon: polygon is NULL"); if( mpDevice ) { mpRecordedActions->push_back( Action() ); Action& rAct=mpRecordedActions->back(); setupGraphicsState( rAct, viewState, renderState ); rAct.maPolyPolys.push_back( ::basegfx::unotools::b2DPolyPolygonFromXPolyPolygon2D(xPolyPolygon)); rAct.maPolyPolys.back().makeUnique(); // own copy, for thread safety // TODO(F1): Multi-texturing if( textures[0].Gradient.is() ) { // try to cast XParametricPolyPolygon2D reference to // our implementation class. ::canvas::ParametricPolyPolygon* pGradient = dynamic_cast< ::canvas::ParametricPolyPolygon* >( textures[0].Gradient.get() ); if( pGradient ) { // copy state from Gradient polypoly locally // (given object might change!) const ::canvas::ParametricPolyPolygon::Values& rValues( pGradient->getValues() ); rAct.maFunction = std::bind(&lcl_fillGradientPolyPolygon, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, rValues, textures[0], std::placeholders::_6); } else { // TODO(F1): The generic case is missing here ENSURE_OR_THROW( false, "CanvasHelper::fillTexturedPolyPolygon(): unknown parametric polygon encountered" ); } } else if( textures[0].Bitmap.is() ) { // own bitmap? CanvasBitmap* pOwnBitmap=dynamic_cast(textures[0].Bitmap.get()); if( pOwnBitmap ) { // TODO(F2): own texture bitmap } else { // TODO(P3): Highly inefficient - simply copies pixel data uno::Reference< rendering::XIntegerReadOnlyBitmap > xIntegerBitmap( textures[0].Bitmap, uno::UNO_QUERY); if( xIntegerBitmap.is() ) { const geometry::IntegerSize2D aSize=xIntegerBitmap->getSize(); rendering::IntegerBitmapLayout aLayout; uno::Sequence aPixelData= xIntegerBitmap->getData( aLayout, geometry::IntegerRectangle2D(0,0,aSize.Width,aSize.Height)); // force-convert color to ARGB8888 int color space uno::Sequence aARGBBytes( aLayout.ColorSpace->convertToIntegerColorSpace( aPixelData, canvas::tools::getStdColorSpace())); rAct.maFunction = std::bind(&lcl_fillTexturedPolyPolygon, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, textures[0], aSize, aARGBBytes, rtl_crc32(0, aARGBBytes.getConstArray(), aARGBBytes.getLength()), std::placeholders::_6); } // TODO(F1): handle non-integer case } } } // TODO(P1): Provide caching here. return uno::Reference< rendering::XCachedPrimitive >(nullptr); } uno::Reference< rendering::XCachedPrimitive > CanvasHelper::fillTextureMappedPolyPolygon( const rendering::XCanvas* /*pCanvas*/, 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::XCanvasFont > CanvasHelper::createFont( const rendering::XCanvas* /*pCanvas*/, const rendering::FontRequest& fontRequest, const uno::Sequence< beans::PropertyValue >& extraFontProperties, const geometry::Matrix2D& fontMatrix ) { if( mpDevice ) return uno::Reference< rendering::XCanvasFont >( new CanvasFont(fontRequest, extraFontProperties, fontMatrix ) ); return uno::Reference< rendering::XCanvasFont >(); } uno::Sequence< rendering::FontInfo > CanvasHelper::queryAvailableFonts( const rendering::XCanvas* /*pCanvas*/, const rendering::FontInfo& /*aFilter*/, const uno::Sequence< beans::PropertyValue >& /*aFontProperties*/ ) { // TODO return uno::Sequence< rendering::FontInfo >(); } uno::Reference< rendering::XCachedPrimitive > CanvasHelper::drawText( const rendering::XCanvas* /*pCanvas*/, const rendering::StringContext& /*text*/, const uno::Reference< rendering::XCanvasFont >& /*xFont*/, const rendering::ViewState& /*viewState*/, const rendering::RenderState& /*renderState*/, sal_Int8 /*textDirection*/ ) { // TODO - but not used from slideshow return uno::Reference< rendering::XCachedPrimitive >(nullptr); } uno::Reference< rendering::XCachedPrimitive > CanvasHelper::drawTextLayout( const rendering::XCanvas* /*pCanvas*/, const uno::Reference< rendering::XTextLayout >& xLayoutetText, const rendering::ViewState& viewState, const rendering::RenderState& renderState ) { ENSURE_OR_THROW( xLayoutetText.is(), "CanvasHelper::drawTextLayout: text is NULL"); if( mpDevice ) { ScopedVclPtrInstance< VirtualDevice > pVDev; pVDev->EnableOutput(false); auto pLayoutFont = xLayoutetText->getFont(); CanvasFont* pFont=dynamic_cast(pLayoutFont.get()); const rendering::StringContext& rTxt=xLayoutetText->getText(); if( pFont && rTxt.Length ) { // create the font const rendering::FontRequest& rFontRequest = pFont->getFontRequest(); const geometry::Matrix2D& rFontMatrix = pFont->getFontMatrix(); vcl::Font aFont( rFontRequest.FontDescription.FamilyName, rFontRequest.FontDescription.StyleName, Size( 0, ::basegfx::fround(rFontRequest.CellSize))); aFont.SetAlignment( ALIGN_BASELINE ); aFont.SetCharSet( (rFontRequest.FontDescription.IsSymbolFont==util::TriState_YES) ? RTL_TEXTENCODING_SYMBOL : RTL_TEXTENCODING_UNICODE ); aFont.SetVertical( rFontRequest.FontDescription.IsVertical==util::TriState_YES ); aFont.SetWeight( static_cast(rFontRequest.FontDescription.FontDescription.Weight) ); aFont.SetItalic( (rFontRequest.FontDescription.FontDescription.Letterform<=8) ? ITALIC_NONE : ITALIC_NORMAL ); if (pFont->getEmphasisMark()) aFont.SetEmphasisMark(FontEmphasisMark(pFont->getEmphasisMark())); // adjust to stretched font if(!::rtl::math::approxEqual(rFontMatrix.m00, rFontMatrix.m11)) { const Size aSize = pVDev->GetFontMetric( aFont ).GetFontSize(); const double fDividend( rFontMatrix.m10 + rFontMatrix.m11 ); double fStretch = rFontMatrix.m00 + rFontMatrix.m01; if( !::basegfx::fTools::equalZero( fDividend) ) fStretch /= fDividend; const sal_Int32 nNewWidth = ::basegfx::fround( aSize.Width() * fStretch ); aFont.SetAverageFontWidth( nNewWidth ); } // set font pVDev->SetFont(aFont); mpRecordedActions->push_back( Action() ); Action& rAct=mpRecordedActions->back(); setupGraphicsState( rAct, viewState, renderState ); // handle custom spacing, if there uno::Sequence aLogicalAdvancements=xLayoutetText->queryLogicalAdvancements(); if( aLogicalAdvancements.hasElements() ) { // create the DXArray const sal_Int32 nLen( aLogicalAdvancements.getLength() ); KernArray aDXArray; aDXArray.resize(nLen); for( sal_Int32 i=0; i aKashidaPositions=xLayoutetText->queryKashidaPositions(); std::span aKashidaArray(aKashidaPositions.getConstArray(), aKashidaPositions.getLength()); // get the glyphs pVDev->GetTextOutlines(rAct.maPolyPolys, rTxt.Text, 0, rTxt.StartPosition, rTxt.Length, 0, aDXArray, aKashidaArray); } else { // get the glyphs pVDev->GetTextOutlines(rAct.maPolyPolys, rTxt.Text, 0, rTxt.StartPosition, rTxt.Length ); } // own copy, for thread safety for( auto& rPoly : rAct.maPolyPolys ) rPoly.makeUnique(); rAct.maFunction = &lcl_fillPolyPolygon; } } // TODO return uno::Reference< rendering::XCachedPrimitive >(nullptr); } uno::Reference< rendering::XCachedPrimitive > CanvasHelper::drawBitmap( const rendering::XCanvas* /*pCanvas*/, const uno::Reference< rendering::XBitmap >& xBitmap, const rendering::ViewState& viewState, const rendering::RenderState& renderState ) { ENSURE_OR_THROW( xBitmap.is(), "CanvasHelper::drawBitmap: bitmap is NULL"); if( mpDevice ) { // own bitmap? CanvasBitmap* pOwnBitmap=dynamic_cast(xBitmap.get()); if( pOwnBitmap ) { // insert as transformed copy of bitmap action vector - // during rendering, this gets rendered into a temporary // buffer, and then composited to the front mpRecordedActions->push_back( Action() ); Action& rAct=mpRecordedActions->back(); setupGraphicsState( rAct, viewState, renderState ); rAct.maFunction = std::bind(&lcl_drawOwnBitmap, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5, *pOwnBitmap); } else { // TODO(P3): Highly inefficient - simply copies pixel data uno::Reference< rendering::XIntegerReadOnlyBitmap > xIntegerBitmap( xBitmap, uno::UNO_QUERY); if( xIntegerBitmap.is() ) { const geometry::IntegerSize2D aSize=xBitmap->getSize(); rendering::IntegerBitmapLayout aLayout; uno::Sequence aPixelData= xIntegerBitmap->getData( aLayout, geometry::IntegerRectangle2D(0,0,aSize.Width,aSize.Height)); // force-convert color to ARGB8888 int color space uno::Sequence aARGBBytes( aLayout.ColorSpace->convertToIntegerColorSpace( aPixelData, canvas::tools::getStdColorSpace())); mpRecordedActions->push_back( Action() ); Action& rAct=mpRecordedActions->back(); setupGraphicsState( rAct, viewState, renderState ); rAct.maFunction = std::bind(&lcl_drawGenericBitmap, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5, aSize, aARGBBytes, rtl_crc32(0, aARGBBytes.getConstArray(), aARGBBytes.getLength())); } // TODO(F1): handle non-integer case } } // TODO(P1): Provide caching here. return uno::Reference< rendering::XCachedPrimitive >(nullptr); } uno::Reference< rendering::XCachedPrimitive > CanvasHelper::drawBitmapModulated( const rendering::XCanvas* pCanvas, const uno::Reference< rendering::XBitmap >& xBitmap, const rendering::ViewState& viewState, const rendering::RenderState& renderState ) { // TODO(F3): remove this wart altogether return drawBitmap(pCanvas, xBitmap, viewState, renderState); } void CanvasHelper::setupGraphicsState( Action& o_action, const rendering::ViewState& viewState, const rendering::RenderState& renderState ) { ENSURE_OR_THROW( mpDevice, "CanvasHelper::setupGraphicsState: reference device invalid" ); // TODO(F3): clipping // TODO(P2): think about caching transformations between canvas calls // setup overall transform only now. View clip above was // relative to view transform ::canvas::tools::mergeViewAndRenderTransform(o_action.maTransform, viewState, renderState); // setup compositing - mapping courtesy David Reveman // (glitz_operator.c) switch( renderState.CompositeOperation ) { case rendering::CompositeOperation::OVER: o_action.meSrcBlendMode=GL_ONE; o_action.meDstBlendMode=GL_ONE_MINUS_SRC_ALPHA; break; case rendering::CompositeOperation::CLEAR: o_action.meSrcBlendMode=GL_ZERO; o_action.meDstBlendMode=GL_ZERO; break; case rendering::CompositeOperation::SOURCE: o_action.meSrcBlendMode=GL_ONE; o_action.meDstBlendMode=GL_ZERO; break; case rendering::CompositeOperation::UNDER: case rendering::CompositeOperation::DESTINATION: o_action.meSrcBlendMode=GL_ZERO; o_action.meDstBlendMode=GL_ONE; break; case rendering::CompositeOperation::INSIDE: o_action.meSrcBlendMode=GL_DST_ALPHA; o_action.meDstBlendMode=GL_ZERO; break; case rendering::CompositeOperation::INSIDE_REVERSE: o_action.meSrcBlendMode=GL_ONE_MINUS_DST_ALPHA; o_action.meDstBlendMode=GL_ZERO; break; case rendering::CompositeOperation::OUTSIDE: o_action.meSrcBlendMode=GL_ONE_MINUS_DST_ALPHA; o_action.meDstBlendMode=GL_ONE; break; case rendering::CompositeOperation::OUTSIDE_REVERSE: o_action.meSrcBlendMode=GL_ZERO; o_action.meDstBlendMode=GL_ONE_MINUS_SRC_ALPHA; break; case rendering::CompositeOperation::ATOP: o_action.meSrcBlendMode=GL_DST_ALPHA; o_action.meDstBlendMode=GL_ONE_MINUS_SRC_ALPHA; break; case rendering::CompositeOperation::ATOP_REVERSE: o_action.meSrcBlendMode=GL_ONE_MINUS_DST_ALPHA; o_action.meDstBlendMode=GL_SRC_ALPHA; break; case rendering::CompositeOperation::XOR: o_action.meSrcBlendMode=GL_ONE_MINUS_DST_ALPHA; o_action.meDstBlendMode=GL_ONE_MINUS_SRC_ALPHA; break; case rendering::CompositeOperation::ADD: o_action.meSrcBlendMode=GL_ONE; o_action.meDstBlendMode=GL_ONE; break; case rendering::CompositeOperation::SATURATE: o_action.meSrcBlendMode=GL_SRC_ALPHA_SATURATE; o_action.meDstBlendMode=GL_SRC_ALPHA_SATURATE; break; default: ENSURE_OR_THROW( false, "CanvasHelper::setupGraphicsState: unexpected mode" ); break; } if (renderState.DeviceColor.hasElements()) o_action.maARGBColor = mpDevice->getDeviceColorSpace()->convertToARGB(renderState.DeviceColor)[0]; } bool CanvasHelper::renderRecordedActions() const { for( const auto& rRecordedAction : *mpRecordedActions ) { if( !rRecordedAction.maFunction( *this, rRecordedAction.maTransform, rRecordedAction.meSrcBlendMode, rRecordedAction.meDstBlendMode, rRecordedAction.maARGBColor, rRecordedAction.maPolyPolys ) ) return false; } return true; } size_t CanvasHelper::getRecordedActionCount() const { return mpRecordedActions->size(); } } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */