diff options
Diffstat (limited to '')
-rw-r--r-- | canvas/source/tools/surface.cxx | 437 |
1 files changed, 437 insertions, 0 deletions
diff --git a/canvas/source/tools/surface.cxx b/canvas/source/tools/surface.cxx new file mode 100644 index 000000000..9dfb0f7c9 --- /dev/null +++ b/canvas/source/tools/surface.cxx @@ -0,0 +1,437 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <basegfx/polygon/b2dpolygonclipper.hxx> +#include <comphelper/scopeguard.hxx> +#include <utility> + +#include "surface.hxx" + +namespace canvas +{ + Surface::Surface( PageManagerSharedPtr rPageManager, + std::shared_ptr<IColorBuffer> xColorBuffer, + const ::basegfx::B2IPoint& rPos, + const ::basegfx::B2ISize& rSize ) : + mpColorBuffer(std::move(xColorBuffer)), + mpPageManager(std::move(rPageManager)), + maSourceOffset(rPos), + maSize(rSize), + mbIsDirty(true) + { + } + + Surface::~Surface() + { + if(mpFragment) + mpPageManager->free(mpFragment); + } + + void Surface::setColorBufferDirty() + { + mbIsDirty=true; + } + + basegfx::B2DRectangle Surface::getUVCoords() const + { + ::basegfx::B2ISize aPageSize(mpPageManager->getPageSize()); + ::basegfx::B2IPoint aDestOffset; + if( mpFragment ) + aDestOffset = mpFragment->getPos(); + + const double pw( aPageSize.getX() ); + const double ph( aPageSize.getY() ); + const double ox( aDestOffset.getX() ); + const double oy( aDestOffset.getY() ); + const double sx( maSize.getX() ); + const double sy( maSize.getY() ); + + return ::basegfx::B2DRectangle( ox/pw, + oy/ph, + (ox+sx)/pw, + (oy+sy)/ph ); + } + + basegfx::B2DRectangle Surface::getUVCoords( const ::basegfx::B2IPoint& rPos, + const ::basegfx::B2ISize& rSize ) const + { + ::basegfx::B2ISize aPageSize(mpPageManager->getPageSize()); + + const double pw( aPageSize.getX() ); + const double ph( aPageSize.getY() ); + const double ox( rPos.getX() ); + const double oy( rPos.getY() ); + const double sx( rSize.getX() ); + const double sy( rSize.getY() ); + + return ::basegfx::B2DRectangle( ox/pw, + oy/ph, + (ox+sx)/pw, + (oy+sy)/ph ); + } + + bool Surface::draw( double fAlpha, + const ::basegfx::B2DPoint& rPos, + const ::basegfx::B2DHomMatrix& rTransform ) + { + std::shared_ptr<IRenderModule> pRenderModule(mpPageManager->getRenderModule()); + + RenderModuleGuard aGuard( pRenderModule ); + + prepareRendering(); + + // convert size to normalized device coordinates + const ::basegfx::B2DRectangle& rUV( getUVCoords() ); + + const double u1(rUV.getMinX()); + const double v1(rUV.getMinY()); + const double u2(rUV.getMaxX()); + const double v2(rUV.getMaxY()); + + // concat transforms + // 1) offset of surface subarea + // 2) surface transform + // 3) translation to output position [rPos] + // 4) scale to normalized device coordinates + // 5) flip y-axis + // 6) translate to account for viewport transform + basegfx::B2DHomMatrix aTransform(basegfx::utils::createTranslateB2DHomMatrix( + maSourceOffset.getX(), maSourceOffset.getY())); + aTransform = aTransform * rTransform; + aTransform.translate(::basegfx::fround(rPos.getX()), + ::basegfx::fround(rPos.getY())); + + /* + ###################################### + ###################################### + ###################################### + + Y + ^+1 + | + 2 | 3 + x------------x + | | | + | | | + ------|-----O------|------>X + -1 | | | +1 + | | | + x------------x + 1 | 0 + | + |-1 + + ###################################### + ###################################### + ###################################### + */ + + const ::basegfx::B2DPoint& p0(aTransform * ::basegfx::B2DPoint(maSize.getX(),maSize.getY())); + const ::basegfx::B2DPoint& p1(aTransform * ::basegfx::B2DPoint(0.0,maSize.getY())); + const ::basegfx::B2DPoint& p2(aTransform * ::basegfx::B2DPoint(0.0,0.0)); + const ::basegfx::B2DPoint& p3(aTransform * ::basegfx::B2DPoint(maSize.getX(),0.0)); + + canvas::Vertex vertex; + vertex.r = 1.0f; + vertex.g = 1.0f; + vertex.b = 1.0f; + vertex.a = static_cast<float>(fAlpha); + vertex.z = 0.0f; + + { + pRenderModule->beginPrimitive( canvas::IRenderModule::PrimitiveType::Quad ); + + // issue an endPrimitive() when leaving the scope + const ::comphelper::ScopeGuard aScopeGuard( + [&pRenderModule]() mutable { pRenderModule->endPrimitive(); } ); + + vertex.u=static_cast<float>(u2); vertex.v=static_cast<float>(v2); + vertex.x=static_cast<float>(p0.getX()); vertex.y=static_cast<float>(p0.getY()); + pRenderModule->pushVertex(vertex); + + vertex.u=static_cast<float>(u1); vertex.v=static_cast<float>(v2); + vertex.x=static_cast<float>(p1.getX()); vertex.y=static_cast<float>(p1.getY()); + pRenderModule->pushVertex(vertex); + + vertex.u=static_cast<float>(u1); vertex.v=static_cast<float>(v1); + vertex.x=static_cast<float>(p2.getX()); vertex.y=static_cast<float>(p2.getY()); + pRenderModule->pushVertex(vertex); + + vertex.u=static_cast<float>(u2); vertex.v=static_cast<float>(v1); + vertex.x=static_cast<float>(p3.getX()); vertex.y=static_cast<float>(p3.getY()); + pRenderModule->pushVertex(vertex); + } + + return !(pRenderModule->isError()); + } + + bool Surface::drawRectangularArea( + double fAlpha, + const ::basegfx::B2DPoint& rPos, + const ::basegfx::B2DRectangle& rArea, + const ::basegfx::B2DHomMatrix& rTransform ) + { + if( rArea.isEmpty() ) + return true; // immediate exit for empty area + + std::shared_ptr<IRenderModule> pRenderModule(mpPageManager->getRenderModule()); + + RenderModuleGuard aGuard( pRenderModule ); + + prepareRendering(); + + // these positions are relative to the texture + ::basegfx::B2IPoint aPos1( + ::basegfx::fround(rArea.getMinimum().getX()), + ::basegfx::fround(rArea.getMinimum().getY())); + ::basegfx::B2IPoint aPos2( + ::basegfx::fround(rArea.getMaximum().getX()), + ::basegfx::fround(rArea.getMaximum().getY()) ); + + // clip the positions to the area this surface covers + aPos1.setX(std::max(aPos1.getX(),maSourceOffset.getX())); + aPos1.setY(std::max(aPos1.getY(),maSourceOffset.getY())); + aPos2.setX(std::min(aPos2.getX(),maSourceOffset.getX()+maSize.getX())); + aPos2.setY(std::min(aPos2.getY(),maSourceOffset.getY()+maSize.getY())); + + // if the resulting area is empty, return immediately + ::basegfx::B2IVector aSize(aPos2 - aPos1); + if(aSize.getX() <= 0 || aSize.getY() <= 0) + return true; + + ::basegfx::B2IPoint aDestOffset; + if( mpFragment ) + aDestOffset = mpFragment->getPos(); + + // convert size to normalized device coordinates + const ::basegfx::B2DRectangle& rUV( + getUVCoords(aPos1 - maSourceOffset + aDestOffset, + aSize) ); + const double u1(rUV.getMinX()); + const double v1(rUV.getMinY()); + const double u2(rUV.getMaxX()); + const double v2(rUV.getMaxY()); + + // concatenate transforms + // 1) offset of surface subarea + // 2) surface transform + // 3) translation to output position [rPos] + basegfx::B2DHomMatrix aTransform(basegfx::utils::createTranslateB2DHomMatrix(aPos1.getX(), aPos1.getY())); + aTransform = aTransform * rTransform; + aTransform.translate(::basegfx::fround(rPos.getX()), + ::basegfx::fround(rPos.getY())); + + + /* + ###################################### + ###################################### + ###################################### + + Y + ^+1 + | + 2 | 3 + x------------x + | | | + | | | + ------|-----O------|------>X + -1 | | | +1 + | | | + x------------x + 1 | 0 + | + |-1 + + ###################################### + ###################################### + ###################################### + */ + + const ::basegfx::B2DPoint& p0(aTransform * ::basegfx::B2DPoint(aSize.getX(),aSize.getY())); + const ::basegfx::B2DPoint& p1(aTransform * ::basegfx::B2DPoint(0.0, aSize.getY())); + const ::basegfx::B2DPoint& p2(aTransform * ::basegfx::B2DPoint(0.0, 0.0)); + const ::basegfx::B2DPoint& p3(aTransform * ::basegfx::B2DPoint(aSize.getX(),0.0)); + + canvas::Vertex vertex; + vertex.r = 1.0f; + vertex.g = 1.0f; + vertex.b = 1.0f; + vertex.a = static_cast<float>(fAlpha); + vertex.z = 0.0f; + + { + pRenderModule->beginPrimitive( canvas::IRenderModule::PrimitiveType::Quad ); + + // issue an endPrimitive() when leaving the scope + const ::comphelper::ScopeGuard aScopeGuard( + [&pRenderModule]() mutable { pRenderModule->endPrimitive(); } ); + + vertex.u=static_cast<float>(u2); vertex.v=static_cast<float>(v2); + vertex.x=static_cast<float>(p0.getX()); vertex.y=static_cast<float>(p0.getY()); + pRenderModule->pushVertex(vertex); + + vertex.u=static_cast<float>(u1); vertex.v=static_cast<float>(v2); + vertex.x=static_cast<float>(p1.getX()); vertex.y=static_cast<float>(p1.getY()); + pRenderModule->pushVertex(vertex); + + vertex.u=static_cast<float>(u1); vertex.v=static_cast<float>(v1); + vertex.x=static_cast<float>(p2.getX()); vertex.y=static_cast<float>(p2.getY()); + pRenderModule->pushVertex(vertex); + + vertex.u=static_cast<float>(u2); vertex.v=static_cast<float>(v1); + vertex.x=static_cast<float>(p3.getX()); vertex.y=static_cast<float>(p3.getY()); + pRenderModule->pushVertex(vertex); + } + + return !(pRenderModule->isError()); + } + + bool Surface::drawWithClip( double fAlpha, + const ::basegfx::B2DPoint& rPos, + const ::basegfx::B2DPolygon& rClipPoly, + const ::basegfx::B2DHomMatrix& rTransform ) + { + std::shared_ptr<IRenderModule> pRenderModule(mpPageManager->getRenderModule()); + + RenderModuleGuard aGuard( pRenderModule ); + + prepareRendering(); + + // untransformed surface rectangle, relative to the whole + // image (note: this surface might actually only be a tile of + // the whole image, with non-zero maSourceOffset) + const double x1(maSourceOffset.getX()); + const double y1(maSourceOffset.getY()); + const double w(maSize.getX()); + const double h(maSize.getY()); + const double x2(x1+w); + const double y2(y1+h); + const ::basegfx::B2DRectangle aSurfaceClipRect(x1,y1,x2,y2); + + // concatenate transforms + // we use 'fround' here to avoid rounding errors. the vertices will + // be transformed by the overall transform and uv coordinates will + // be calculated from the result, and this is why we need to use + // integer coordinates here... + basegfx::B2DHomMatrix aTransform = rTransform; + aTransform.translate(::basegfx::fround(rPos.getX()), + ::basegfx::fround(rPos.getY())); + + /* + ###################################### + ###################################### + ###################################### + + Y + ^+1 + | + 2 | 3 + x------------x + | | | + | | | + ------|-----O------|------>X + -1 | | | +1 + | | | + x------------x + 1 | 0 + | + |-1 + + ###################################### + ###################################### + ###################################### + */ + + // uv coordinates that map the surface rectangle + // to the destination rectangle. + const ::basegfx::B2DRectangle& rUV( getUVCoords() ); + + basegfx::B2DPolygon rTriangleList(basegfx::utils::clipTriangleListOnRange(rClipPoly, + aSurfaceClipRect)); + + // Push vertices to backend renderer + if(const sal_uInt32 nVertexCount = rTriangleList.count()) + { + canvas::Vertex vertex; + vertex.r = 1.0f; + vertex.g = 1.0f; + vertex.b = 1.0f; + vertex.a = static_cast<float>(fAlpha); + vertex.z = 0.0f; + + pRenderModule->beginPrimitive( canvas::IRenderModule::PrimitiveType::Triangle ); + + // issue an endPrimitive() when leaving the scope + const ::comphelper::ScopeGuard aScopeGuard( + [&pRenderModule]() mutable { pRenderModule->endPrimitive(); } ); + + for(sal_uInt32 nIndex=0; nIndex<nVertexCount; ++nIndex) + { + const basegfx::B2DPoint &aPoint = rTriangleList.getB2DPoint(nIndex); + basegfx::B2DPoint aTransformedPoint(aTransform * aPoint); + const double tu(((aPoint.getX()-aSurfaceClipRect.getMinX())*rUV.getWidth()/w)+rUV.getMinX()); + const double tv(((aPoint.getY()-aSurfaceClipRect.getMinY())*rUV.getHeight()/h)+rUV.getMinY()); + vertex.u=static_cast<float>(tu); + vertex.v=static_cast<float>(tv); + vertex.x=static_cast<float>(aTransformedPoint.getX()); + vertex.y=static_cast<float>(aTransformedPoint.getY()); + pRenderModule->pushVertex(vertex); + } + } + + return !(pRenderModule->isError()); + } + + void Surface::prepareRendering() + { + mpPageManager->validatePages(); + + // clients requested to draw from this surface, therefore one + // of the above implemented concrete rendering operations + // was triggered. we therefore need to ask the pagemanager + // to allocate some space for the fragment we're dedicated to. + if(!mpFragment) + { + mpFragment = mpPageManager->allocateSpace(maSize); + if( mpFragment ) + { + mpFragment->setColorBuffer(mpColorBuffer); + mpFragment->setSourceOffset(maSourceOffset); + } + } + + if( mpFragment ) + { + // now we need to 'select' the fragment, which will in turn + // pull information from the image on demand. + // in case this fragment is still not located on any of the + // available pages ['naked'], we force the page manager to + // do it now, no way to defer this any longer... + if(!(mpFragment->select(mbIsDirty))) + mpPageManager->nakedFragment(mpFragment); + + } + mbIsDirty=false; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |