/* -*- 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: */
↑ V530 The return value of function 'homMatrixFromAffineMatrix' is required to be utilized.
↑ V530 The return value of function 'homMatrixFromAffineMatrix' is required to be utilized.
↑ V530 The return value of function 'mergeViewAndRenderTransform' is required to be utilized.