/* -*- 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 #include #include #include #include #include "surface.hxx" namespace canvas { Surface::Surface( PageManagerSharedPtr rPageManager, std::shared_ptr 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.getWidth() ); const double ph( aPageSize.getHeight() ); const double ox( aDestOffset.getX() ); const double oy( aDestOffset.getY() ); const double sx( maSize.getWidth() ); const double sy( maSize.getHeight() ); 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.getWidth() ); const double ph( aPageSize.getHeight() ); const double ox( rPos.getX() ); const double oy( rPos.getY() ); const double sx( rSize.getWidth() ); const double sy( rSize.getHeight() ); 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 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.getWidth(),maSize.getHeight())); const ::basegfx::B2DPoint& p1(aTransform * ::basegfx::B2DPoint(0.0,maSize.getHeight())); const ::basegfx::B2DPoint& p2(aTransform * ::basegfx::B2DPoint(0.0,0.0)); const ::basegfx::B2DPoint& p3(aTransform * ::basegfx::B2DPoint(maSize.getWidth(),0.0)); canvas::Vertex vertex; vertex.r = 1.0f; vertex.g = 1.0f; vertex.b = 1.0f; vertex.a = static_cast(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(u2); vertex.v=static_cast(v2); vertex.x=static_cast(p0.getX()); vertex.y=static_cast(p0.getY()); pRenderModule->pushVertex(vertex); vertex.u=static_cast(u1); vertex.v=static_cast(v2); vertex.x=static_cast(p1.getX()); vertex.y=static_cast(p1.getY()); pRenderModule->pushVertex(vertex); vertex.u=static_cast(u1); vertex.v=static_cast(v1); vertex.x=static_cast(p2.getX()); vertex.y=static_cast(p2.getY()); pRenderModule->pushVertex(vertex); vertex.u=static_cast(u2); vertex.v=static_cast(v1); vertex.x=static_cast(p3.getX()); vertex.y=static_cast(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 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.getWidth())); aPos2.setY(std::min(aPos2.getY(), maSourceOffset.getY() + maSize.getHeight())); // 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, basegfx::B2ISize(aSize.getX(), aSize.getY())) ); 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(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(u2); vertex.v=static_cast(v2); vertex.x=static_cast(p0.getX()); vertex.y=static_cast(p0.getY()); pRenderModule->pushVertex(vertex); vertex.u=static_cast(u1); vertex.v=static_cast(v2); vertex.x=static_cast(p1.getX()); vertex.y=static_cast(p1.getY()); pRenderModule->pushVertex(vertex); vertex.u=static_cast(u1); vertex.v=static_cast(v1); vertex.x=static_cast(p2.getX()); vertex.y=static_cast(p2.getY()); pRenderModule->pushVertex(vertex); vertex.u=static_cast(u2); vertex.v=static_cast(v1); vertex.x=static_cast(p3.getX()); vertex.y=static_cast(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 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.getWidth()); const double h(maSize.getHeight()); 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(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(tu); vertex.v=static_cast(tv); vertex.x=static_cast(aTransformedPoint.getX()); vertex.y=static_cast(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: */