/* -*- 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 <sal/config.h>
#include <memory>
#include <functional>
#include <epoxy/gl.h>
#include <basegfx/matrix/b2dhommatrix.hxx>
#include <basegfx/polygon/b2dpolypolygon.hxx>
#include <basegfx/polygon/b2dpolypolygontools.hxx>
#include <basegfx/utils/canvastools.hxx>
#include <com/sun/star/rendering/CompositeOperation.hpp>
#include <rtl/crc.h>
#include <rtl/math.hxx>
#include <comphelper/diagnose_ex.hxx>
#include <vcl/font.hxx>
#include <vcl/kernarray.hxx>
#include <vcl/metric.hxx>
#include <vcl/virdev.hxx>
#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 ; i<nNumCols; ++i )
*pCurrCol++ = rHelper.getDevice()->getDeviceColorSpace()->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<sal_Int8>& 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<sal_Int8>& 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 aValues(
pGradient->getValues() );
rAct.maFunction = std::bind(&lcl_fillGradientPolyPolygon,
std::placeholders::_1 , std::placeholders::_2 , std::placeholders::_3 , std::placeholders::_4 ,
aValues,
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 <CanvasBitmap*>(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<sal_Int8> aPixelData=
xIntegerBitmap->getData(
aLayout,
geometry::IntegerRectangle2D(0 ,0 ,aSize.Width,aSize.Height));
// force-convert color to ARGB8888 int color space
uno::Sequence<sal_Int8> 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 <CanvasFont*>(pLayoutFont.get());
const rendering::StringContext aTxt=xLayoutetText->getText();
if ( pFont && aTxt.Length )
{
// create the font
const rendering::FontRequest aFontRequest = pFont->getFontRequest();
const geometry::Matrix2D& rFontMatrix = pFont->getFontMatrix();
vcl::Font aFont(
aFontRequest.FontDescription.FamilyName,
aFontRequest.FontDescription.StyleName,
Size( 0 , ::basegfx::fround<tools::Long >(aFontRequest.CellSize)));
aFont.SetAlignment( ALIGN_BASELINE );
aFont.SetCharSet( (aFontRequest.FontDescription.IsSymbolFont==util::TriState_YES) ? RTL_TEXTENCODING_SYMBOL : RTL_TEXTENCODING_UNICODE );
aFont.SetVertical( aFontRequest.FontDescription.IsVertical==util::TriState_YES );
aFont.SetWeight( static_cast <FontWeight>(aFontRequest.FontDescription.FontDescription.Weight) );
aFont.SetItalic( (aFontRequest.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<double > aLogicalAdvancements=xLayoutetText->queryLogicalAdvancements();
if ( aLogicalAdvancements.hasElements() )
{
KernArraySpan aDXArray(aLogicalAdvancements.getConstArray(), aLogicalAdvancements.getLength());
uno::Sequence<sal_Bool> aKashidaPositions=xLayoutetText->queryKashidaPositions();
std::span<const sal_Bool> aKashidaArray(aKashidaPositions.getConstArray(), aKashidaPositions.getLength());
// get the glyphs
pVDev->GetTextOutlines(rAct.maPolyPolys,
aTxt.Text,
0 ,
aTxt.StartPosition,
aTxt.Length,
0 ,
aDXArray,
aKashidaArray);
}
else
{
// get the glyphs
pVDev->GetTextOutlines(rAct.maPolyPolys,
aTxt.Text,
0 ,
aTxt.StartPosition,
aTxt.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 <CanvasBitmap*>(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<sal_Int8> aPixelData=
xIntegerBitmap->getData(
aLayout,
geometry::IntegerRectangle2D(0 ,0 ,aSize.Width,aSize.Height));
// force-convert color to ARGB8888 int color space
uno::Sequence<sal_Int8> 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: */
Messung V0.5 in Prozent C=93 H=93 G=92
¤ Dauer der Verarbeitung: 0.28 Sekunden
(vorverarbeitet am 2026-09-27)
¤
*© Formatika GbR, Deutschland