From 940b4d1848e8c70ab7642901a68594e8016caffc Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sat, 27 Apr 2024 18:51:28 +0200 Subject: Adding upstream version 1:7.0.4. Signed-off-by: Daniel Baumann --- canvas/source/tools/cachedprimitivebase.cxx | 95 ++ canvas/source/tools/canvascustomspritehelper.cxx | 447 ++++++++ canvas/source/tools/canvastools.cxx | 1326 ++++++++++++++++++++++ canvas/source/tools/elapsedtime.cxx | 129 +++ canvas/source/tools/page.cxx | 135 +++ canvas/source/tools/page.hxx | 150 +++ canvas/source/tools/pagemanager.cxx | 152 +++ canvas/source/tools/pagemanager.hxx | 78 ++ canvas/source/tools/parametricpolypolygon.cxx | 245 ++++ canvas/source/tools/propertysethelper.cxx | 156 +++ canvas/source/tools/spriteredrawmanager.cxx | 489 ++++++++ canvas/source/tools/surface.cxx | 437 +++++++ canvas/source/tools/surface.hxx | 146 +++ canvas/source/tools/surfaceproxy.cxx | 141 +++ canvas/source/tools/surfaceproxy.hxx | 121 ++ canvas/source/tools/surfaceproxymanager.cxx | 73 ++ canvas/source/tools/surfacerect.hxx | 93 ++ canvas/source/tools/verifyinput.cxx | 713 ++++++++++++ 18 files changed, 5126 insertions(+) create mode 100644 canvas/source/tools/cachedprimitivebase.cxx create mode 100644 canvas/source/tools/canvascustomspritehelper.cxx create mode 100644 canvas/source/tools/canvastools.cxx create mode 100644 canvas/source/tools/elapsedtime.cxx create mode 100644 canvas/source/tools/page.cxx create mode 100644 canvas/source/tools/page.hxx create mode 100644 canvas/source/tools/pagemanager.cxx create mode 100644 canvas/source/tools/pagemanager.hxx create mode 100644 canvas/source/tools/parametricpolypolygon.cxx create mode 100644 canvas/source/tools/propertysethelper.cxx create mode 100644 canvas/source/tools/spriteredrawmanager.cxx create mode 100644 canvas/source/tools/surface.cxx create mode 100644 canvas/source/tools/surface.hxx create mode 100644 canvas/source/tools/surfaceproxy.cxx create mode 100644 canvas/source/tools/surfaceproxy.hxx create mode 100644 canvas/source/tools/surfaceproxymanager.cxx create mode 100644 canvas/source/tools/surfacerect.hxx create mode 100644 canvas/source/tools/verifyinput.cxx (limited to 'canvas/source/tools') diff --git a/canvas/source/tools/cachedprimitivebase.cxx b/canvas/source/tools/cachedprimitivebase.cxx new file mode 100644 index 000000000..056796fcd --- /dev/null +++ b/canvas/source/tools/cachedprimitivebase.cxx @@ -0,0 +1,95 @@ +/* -*- 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 + + +using namespace ::com::sun::star; + +namespace canvas +{ + CachedPrimitiveBase::CachedPrimitiveBase( const rendering::ViewState& rUsedViewState, + const uno::Reference< rendering::XCanvas >& rTarget ) : + CachedPrimitiveBase_Base( m_aMutex ), + maUsedViewState( rUsedViewState ), + mxTarget( rTarget ) + { + } + + CachedPrimitiveBase::~CachedPrimitiveBase() + { + } + + void SAL_CALL CachedPrimitiveBase::disposing() + { + ::osl::MutexGuard aGuard( m_aMutex ); + + maUsedViewState.Clip.clear(); + mxTarget.clear(); + } + + sal_Int8 SAL_CALL CachedPrimitiveBase::redraw( const rendering::ViewState& aState ) + { + ::basegfx::B2DHomMatrix aUsedTransformation; + ::basegfx::B2DHomMatrix aNewTransformation; + + ::basegfx::unotools::homMatrixFromAffineMatrix( aUsedTransformation, + maUsedViewState.AffineTransform ); + ::basegfx::unotools::homMatrixFromAffineMatrix( aNewTransformation, + aState.AffineTransform ); + + const bool bSameViewTransforms( aUsedTransformation == aNewTransformation ); + + if( !bSameViewTransforms ) + { + // differing transformations, don't try to draft the + // output, just plain fail here. + return rendering::RepaintResult::FAILED; + } + + return doRedraw( aState, + maUsedViewState, + mxTarget, + bSameViewTransforms ); + } + + OUString SAL_CALL CachedPrimitiveBase::getImplementationName( ) + { + return "canvas::CachedPrimitiveBase"; + } + + sal_Bool SAL_CALL CachedPrimitiveBase::supportsService( const OUString& ServiceName ) + { + return cppu::supportsService(this, ServiceName); + } + + uno::Sequence< OUString > SAL_CALL CachedPrimitiveBase::getSupportedServiceNames( ) + { + return { "com.sun.star.rendering.CachedBitmap" }; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/tools/canvascustomspritehelper.cxx b/canvas/source/tools/canvascustomspritehelper.cxx new file mode 100644 index 000000000..ef02a9d5a --- /dev/null +++ b/canvas/source/tools/canvascustomspritehelper.cxx @@ -0,0 +1,447 @@ +/* -*- 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 +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +using namespace ::com::sun::star; + + +namespace canvas +{ + bool CanvasCustomSpriteHelper::updateClipState( const Sprite::Reference& rSprite ) + { + if( !mxClipPoly.is() ) + { + // empty clip polygon -> everything is visible now + maCurrClipBounds.reset(); + mbIsCurrClipRectangle = true; + } + else + { + const sal_Int32 nNumClipPolygons( mxClipPoly->getNumberOfPolygons() ); + + // clip is not empty - determine actual update area + ::basegfx::B2DPolyPolygon aClipPath( + polyPolygonFromXPolyPolygon2D( mxClipPoly ) ); + + // apply sprite transformation also to clip! + aClipPath.transform( maTransform ); + + // clip which is about to be set, expressed as a + // b2drectangle + const ::basegfx::B2DRectangle& rClipBounds( + ::basegfx::utils::getRange( aClipPath ) ); + + const ::basegfx::B2DRectangle aBounds( 0.0, 0.0, + maSize.getX(), + maSize.getY() ); + + // rectangular area which is actually covered by the sprite. + // coordinates are relative to the sprite origin. + ::basegfx::B2DRectangle aSpriteRectPixel; + ::canvas::tools::calcTransformedRectBounds( aSpriteRectPixel, + aBounds, + maTransform ); + + // aClipBoundsA = new clip bound rect, intersected + // with sprite area + ::basegfx::B2DRectangle aClipBoundsA(rClipBounds); + aClipBoundsA.intersect( aSpriteRectPixel ); + + if( nNumClipPolygons != 1 ) + { + // clip cannot be a single rectangle -> cannot + // optimize update + mbIsCurrClipRectangle = false; + maCurrClipBounds = aClipBoundsA; + } + else + { + // new clip could be a single rectangle - check + // that now: + const bool bNewClipIsRect( + ::basegfx::utils::isRectangle( aClipPath.getB2DPolygon(0) ) ); + + // both new and old clip are truly rectangles + // - can now take the optimized path + const bool bUseOptimizedUpdate( bNewClipIsRect && + mbIsCurrClipRectangle ); + + const ::basegfx::B2DRectangle aOldBounds( maCurrClipBounds ); + + // store new current clip type + maCurrClipBounds = aClipBoundsA; + mbIsCurrClipRectangle = bNewClipIsRect; + + if( mbActive && + bUseOptimizedUpdate ) + { + // aClipBoundsB = maCurrClipBounds, i.e. last + // clip, intersected with sprite area + std::vector< ::basegfx::B2DRectangle > aClipDifferences; + + // get all rectangles covered by exactly one + // of the polygons (aka XOR) + ::basegfx::computeSetDifference(aClipDifferences, + aClipBoundsA, + aOldBounds); + + // aClipDifferences now contains the final + // update areas, coordinates are still relative + // to the sprite origin. before submitting + // this area to 'updateSprite()' we need to + // translate this area to the final position, + // coordinates need to be relative to the + // spritecanvas. + for( const auto& rClipDiff : aClipDifferences ) + { + mpSpriteCanvas->updateSprite( + rSprite, + maPosition, + ::basegfx::B2DRectangle( + maPosition + rClipDiff.getMinimum(), + maPosition + rClipDiff.getMaximum() ) ); + } + + // update calls all done + return true; + } + } + } + + // caller needs to perform update calls + return false; + } + + CanvasCustomSpriteHelper::CanvasCustomSpriteHelper() : + mpSpriteCanvas(), + maCurrClipBounds(), + maPosition(), + maSize(), + maTransform(), + mxClipPoly(), + mfPriority(0.0), + mfAlpha(0.0), + mbActive(false), + mbIsCurrClipRectangle(true), + mbIsContentFullyOpaque( false ), + mbTransformDirty( true ) + { + } + + void CanvasCustomSpriteHelper::init( const geometry::RealSize2D& rSpriteSize, + const SpriteSurface::Reference& rOwningSpriteCanvas ) + { + ENSURE_OR_THROW( rOwningSpriteCanvas.get(), + "CanvasCustomSpriteHelper::init(): Invalid owning sprite canvas" ); + + mpSpriteCanvas = rOwningSpriteCanvas; + maSize.setX( std::max( 1.0, + ceil( rSpriteSize.Width ) ) ); // round up to nearest int, + // enforce sprite to have at + // least (1,1) pixel size + maSize.setY( std::max( 1.0, + ceil( rSpriteSize.Height ) ) ); + } + + void CanvasCustomSpriteHelper::disposing() + { + mpSpriteCanvas.clear(); + } + + void CanvasCustomSpriteHelper::clearingContent( const Sprite::Reference& /*rSprite*/ ) + { + // about to clear content to fully transparent + mbIsContentFullyOpaque = false; + } + + void CanvasCustomSpriteHelper::checkDrawBitmap( const Sprite::Reference& rSprite, + const uno::Reference< rendering::XBitmap >& xBitmap, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState ) + { + // check whether bitmap is non-alpha, and whether its + // transformed size covers the whole sprite. + if( xBitmap->hasAlpha() ) + return; + + const geometry::IntegerSize2D& rInputSize( + xBitmap->getSize() ); + const ::basegfx::B2DSize& rOurSize( + rSprite->getSizePixel() ); + + ::basegfx::B2DHomMatrix aTransform; + if( tools::isInside( + ::basegfx::B2DRectangle( 0.0,0.0, + rOurSize.getX(), + rOurSize.getY() ), + ::basegfx::B2DRectangle( 0.0,0.0, + rInputSize.Width, + rInputSize.Height ), + ::canvas::tools::mergeViewAndRenderTransform(aTransform, + viewState, + renderState) ) ) + { + // bitmap is opaque and will fully cover the sprite, + // set flag appropriately + mbIsContentFullyOpaque = true; + } + } + + void CanvasCustomSpriteHelper::setAlpha( const Sprite::Reference& rSprite, + double alpha ) + { + if( !mpSpriteCanvas ) + return; // we're disposed + + if( alpha != mfAlpha ) + { + mfAlpha = alpha; + + if( mbActive ) + { + mpSpriteCanvas->updateSprite( rSprite, + maPosition, + getUpdateArea() ); + } + } + } + + void CanvasCustomSpriteHelper::move( const Sprite::Reference& rSprite, + const geometry::RealPoint2D& aNewPos, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState ) + { + if( !mpSpriteCanvas ) + return; // we're disposed + + ::basegfx::B2DHomMatrix aTransform; + ::canvas::tools::mergeViewAndRenderTransform(aTransform, + viewState, + renderState); + + // convert position to device pixel + ::basegfx::B2DPoint aPoint( + ::basegfx::unotools::b2DPointFromRealPoint2D(aNewPos) ); + aPoint *= aTransform; + + if( aPoint == maPosition ) + return; + + const ::basegfx::B2DRectangle& rBounds + = getUpdateArea( ::basegfx::B2DRectangle( 0.0, 0.0, + maSize.getX(), + maSize.getY() ) ); + + if( mbActive ) + { + mpSpriteCanvas->moveSprite( rSprite, + rBounds.getMinimum(), + rBounds.getMinimum() - maPosition + aPoint, + rBounds.getRange() ); + } + + maPosition = aPoint; + } + + void CanvasCustomSpriteHelper::transform( const Sprite::Reference& rSprite, + const geometry::AffineMatrix2D& aTransformation ) + { + ::basegfx::B2DHomMatrix aMatrix; + ::basegfx::unotools::homMatrixFromAffineMatrix(aMatrix, + aTransformation); + + if( maTransform == aMatrix ) + return; + + // retrieve bounds before and after transformation change. + const ::basegfx::B2DRectangle& rPrevBounds( getUpdateArea() ); + + maTransform = aMatrix; + + if( !updateClipState( rSprite ) && + mbActive ) + { + mpSpriteCanvas->updateSprite( rSprite, + maPosition, + rPrevBounds ); + mpSpriteCanvas->updateSprite( rSprite, + maPosition, + getUpdateArea() ); + } + + mbTransformDirty = true; + } + + void CanvasCustomSpriteHelper::clip( const Sprite::Reference& rSprite, + const uno::Reference< rendering::XPolyPolygon2D >& xClip ) + { + // NULL xClip explicitly allowed here (to clear clipping) + + // retrieve bounds before and after clip change. + const ::basegfx::B2DRectangle& rPrevBounds( getUpdateArea() ); + + mxClipPoly = xClip; + + if( !updateClipState( rSprite ) && + mbActive ) + { + mpSpriteCanvas->updateSprite( rSprite, + maPosition, + rPrevBounds ); + mpSpriteCanvas->updateSprite( rSprite, + maPosition, + getUpdateArea() ); + } + } + + void CanvasCustomSpriteHelper::setPriority( const Sprite::Reference& rSprite, + double nPriority ) + { + if( !mpSpriteCanvas ) + return; // we're disposed + + if( nPriority != mfPriority ) + { + mfPriority = nPriority; + + if( mbActive ) + { + mpSpriteCanvas->updateSprite( rSprite, + maPosition, + getUpdateArea() ); + } + } + } + + void CanvasCustomSpriteHelper::show( const Sprite::Reference& rSprite ) + { + if( !mpSpriteCanvas ) + return; // we're disposed + + if( mbActive ) + return; + + mpSpriteCanvas->showSprite( rSprite ); + mbActive = true; + + // TODO(P1): if clip is the NULL clip (nothing visible), + // also save us the update call. + + if( mfAlpha != 0.0 ) + { + mpSpriteCanvas->updateSprite( rSprite, + maPosition, + getUpdateArea() ); + } + } + + void CanvasCustomSpriteHelper::hide( const Sprite::Reference& rSprite ) + { + if( !mpSpriteCanvas ) + return; // we're disposed + + if( !mbActive ) + return; + + mpSpriteCanvas->hideSprite( rSprite ); + mbActive = false; + + // TODO(P1): if clip is the NULL clip (nothing visible), + // also save us the update call. + + if( mfAlpha != 0.0 ) + { + mpSpriteCanvas->updateSprite( rSprite, + maPosition, + getUpdateArea() ); + } + } + + bool CanvasCustomSpriteHelper::isAreaUpdateOpaque( const ::basegfx::B2DRange& rUpdateArea ) const + { + if( !mbIsCurrClipRectangle || + !mbIsContentFullyOpaque || + !::rtl::math::approxEqual(mfAlpha, 1.0) ) + { + // sprite either transparent, or clip rect does not + // represent exact bounds -> update might not be fully + // opaque + return false; + } + else + { + // make sure sprite rect fully covers update area - + // although the update area originates from the sprite, + // it's by no means guaranteed that it's limited to this + // sprite's update area - after all, other sprites might + // have been merged, or this sprite is moving. + return getUpdateArea().isInside( rUpdateArea ); + } + } + + ::basegfx::B2DRange CanvasCustomSpriteHelper::getUpdateArea( const ::basegfx::B2DRange& rBounds ) const + { + // Internal! Only call with locked object mutex! + ::basegfx::B2DHomMatrix aTransform( maTransform ); + aTransform.translate( maPosition.getX(), + maPosition.getY() ); + + // transform bounds at origin, as the sprite transformation is + // formulated that way + ::basegfx::B2DRectangle aTransformedBounds; + return ::canvas::tools::calcTransformedRectBounds( aTransformedBounds, + rBounds, + aTransform ); + } + + ::basegfx::B2DRange CanvasCustomSpriteHelper::getUpdateArea() const + { + // Internal! Only call with locked object mutex! + + // return effective sprite rect, i.e. take active clip into + // account + if( maCurrClipBounds.isEmpty() ) + return getUpdateArea( ::basegfx::B2DRectangle( 0.0, 0.0, + maSize.getX(), + maSize.getY() ) ); + else + return ::basegfx::B2DRectangle( + maPosition + maCurrClipBounds.getMinimum(), + maPosition + maCurrClipBounds.getMaximum() ); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/tools/canvastools.cxx b/canvas/source/tools/canvastools.cxx new file mode 100644 index 000000000..53ab7e71f --- /dev/null +++ b/canvas/source/tools/canvastools.cxx @@ -0,0 +1,1326 @@ +/* -*- 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + + +using namespace ::com::sun::star; + +namespace canvas::tools +{ + geometry::RealSize2D createInfiniteSize2D() + { + return geometry::RealSize2D( + std::numeric_limits::infinity(), + std::numeric_limits::infinity() ); + } + + rendering::RenderState& initRenderState( rendering::RenderState& renderState ) + { + // setup identity transform + setIdentityAffineMatrix2D( renderState.AffineTransform ); + renderState.Clip.clear(); + renderState.DeviceColor = uno::Sequence< double >(); + renderState.CompositeOperation = rendering::CompositeOperation::OVER; + + return renderState; + } + + rendering::ViewState& initViewState( rendering::ViewState& viewState ) + { + // setup identity transform + setIdentityAffineMatrix2D( viewState.AffineTransform ); + viewState.Clip.clear(); + + return viewState; + } + + ::basegfx::B2DHomMatrix& getViewStateTransform( ::basegfx::B2DHomMatrix& transform, + const rendering::ViewState& viewState ) + { + return ::basegfx::unotools::homMatrixFromAffineMatrix( transform, viewState.AffineTransform ); + } + + rendering::ViewState& setViewStateTransform( rendering::ViewState& viewState, + const ::basegfx::B2DHomMatrix& transform ) + { + ::basegfx::unotools::affineMatrixFromHomMatrix( viewState.AffineTransform, transform ); + + return viewState; + } + + ::basegfx::B2DHomMatrix& getRenderStateTransform( ::basegfx::B2DHomMatrix& transform, + const rendering::RenderState& renderState ) + { + return ::basegfx::unotools::homMatrixFromAffineMatrix( transform, renderState.AffineTransform ); + } + + rendering::RenderState& setRenderStateTransform( rendering::RenderState& renderState, + const ::basegfx::B2DHomMatrix& transform ) + { + ::basegfx::unotools::affineMatrixFromHomMatrix( renderState.AffineTransform, transform ); + + return renderState; + } + + rendering::RenderState& appendToRenderState( rendering::RenderState& renderState, + const ::basegfx::B2DHomMatrix& rTransform ) + { + ::basegfx::B2DHomMatrix transform; + + getRenderStateTransform( transform, renderState ); + return setRenderStateTransform( renderState, transform * rTransform ); + } + + rendering::RenderState& prependToRenderState( rendering::RenderState& renderState, + const ::basegfx::B2DHomMatrix& rTransform ) + { + ::basegfx::B2DHomMatrix transform; + + getRenderStateTransform( transform, renderState ); + return setRenderStateTransform( renderState, rTransform * transform ); + } + + ::basegfx::B2DHomMatrix& mergeViewAndRenderTransform( ::basegfx::B2DHomMatrix& combinedTransform, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState ) + { + ::basegfx::B2DHomMatrix viewTransform; + + ::basegfx::unotools::homMatrixFromAffineMatrix( combinedTransform, renderState.AffineTransform ); + ::basegfx::unotools::homMatrixFromAffineMatrix( viewTransform, viewState.AffineTransform ); + + // this statement performs combinedTransform = viewTransform * combinedTransform + combinedTransform *= viewTransform; + + return combinedTransform; + } + + geometry::AffineMatrix2D& setIdentityAffineMatrix2D( geometry::AffineMatrix2D& matrix ) + { + matrix.m00 = 1.0; + matrix.m01 = 0.0; + matrix.m02 = 0.0; + matrix.m10 = 0.0; + matrix.m11 = 1.0; + matrix.m12 = 0.0; + + return matrix; + } + + geometry::Matrix2D& setIdentityMatrix2D( geometry::Matrix2D& matrix ) + { + matrix.m00 = 1.0; + matrix.m01 = 0.0; + matrix.m10 = 0.0; + matrix.m11 = 1.0; + + return matrix; + } + + namespace + { + class StandardColorSpace : public cppu::WeakImplHelper< css::rendering::XIntegerBitmapColorSpace > + { + private: + uno::Sequence< sal_Int8 > maComponentTags; + uno::Sequence< sal_Int32 > maBitCounts; + + virtual ::sal_Int8 SAL_CALL getType( ) override + { + return rendering::ColorSpaceType::RGB; + } + virtual uno::Sequence< ::sal_Int8 > SAL_CALL getComponentTags( ) override + { + return maComponentTags; + } + virtual ::sal_Int8 SAL_CALL getRenderingIntent( ) override + { + return rendering::RenderingIntent::PERCEPTUAL; + } + virtual uno::Sequence< beans::PropertyValue > SAL_CALL getProperties( ) override + { + return uno::Sequence< beans::PropertyValue >(); + } + virtual uno::Sequence< double > SAL_CALL convertColorSpace( const uno::Sequence< double >& deviceColor, + const uno::Reference< rendering::XColorSpace >& targetColorSpace ) override + { + // TODO(P3): if we know anything about target + // colorspace, this can be greatly sped up + uno::Sequence aIntermediate( + convertToARGB(deviceColor)); + return targetColorSpace->convertFromARGB(aIntermediate); + } + virtual uno::Sequence< rendering::RGBColor > SAL_CALL convertToRGB( const uno::Sequence< double >& deviceColor ) override + { + const double* pIn( deviceColor.getConstArray() ); + const std::size_t nLen( deviceColor.getLength() ); + ENSURE_ARG_OR_THROW2(nLen%4==0, + "number of channels no multiple of 4", + static_cast(this), 0); + + uno::Sequence< rendering::RGBColor > aRes(nLen/4); + rendering::RGBColor* pOut( aRes.getArray() ); + for( std::size_t i=0; i SAL_CALL convertToARGB( const uno::Sequence< double >& deviceColor ) override + { + SAL_WARN_IF(!deviceColor.hasElements(), "canvas", "empty deviceColor argument"); + const double* pIn( deviceColor.getConstArray() ); + const std::size_t nLen( deviceColor.getLength() ); + ENSURE_ARG_OR_THROW2(nLen%4==0, + "number of channels no multiple of 4", + static_cast(this), 0); + + uno::Sequence< rendering::ARGBColor > aRes(nLen/4); + rendering::ARGBColor* pOut( aRes.getArray() ); + for( std::size_t i=0; i SAL_CALL convertToPARGB( const uno::Sequence< double >& deviceColor ) override + { + const double* pIn( deviceColor.getConstArray() ); + const std::size_t nLen( deviceColor.getLength() ); + ENSURE_ARG_OR_THROW2(nLen%4==0, + "number of channels no multiple of 4", + static_cast(this), 0); + + uno::Sequence< rendering::ARGBColor > aRes(nLen/4); + rendering::ARGBColor* pOut( aRes.getArray() ); + for( std::size_t i=0; i SAL_CALL convertFromRGB( const uno::Sequence< rendering::RGBColor >& rgbColor ) override + { + const rendering::RGBColor* pIn( rgbColor.getConstArray() ); + const std::size_t nLen( rgbColor.getLength() ); + + uno::Sequence< double > aRes(nLen*4); + double* pColors=aRes.getArray(); + for( std::size_t i=0; iRed; + *pColors++ = pIn->Green; + *pColors++ = pIn->Blue; + *pColors++ = 1.0; + ++pIn; + } + return aRes; + } + virtual uno::Sequence< double > SAL_CALL convertFromARGB( const uno::Sequence< rendering::ARGBColor >& rgbColor ) override + { + const rendering::ARGBColor* pIn( rgbColor.getConstArray() ); + const std::size_t nLen( rgbColor.getLength() ); + + uno::Sequence< double > aRes(nLen*4); + double* pColors=aRes.getArray(); + for( std::size_t i=0; iRed; + *pColors++ = pIn->Green; + *pColors++ = pIn->Blue; + *pColors++ = pIn->Alpha; + ++pIn; + } + return aRes; + } + virtual uno::Sequence< double > SAL_CALL convertFromPARGB( const uno::Sequence< rendering::ARGBColor >& rgbColor ) override + { + const rendering::ARGBColor* pIn( rgbColor.getConstArray() ); + const std::size_t nLen( rgbColor.getLength() ); + + uno::Sequence< double > aRes(nLen*4); + double* pColors=aRes.getArray(); + for( std::size_t i=0; iRed/pIn->Alpha; + *pColors++ = pIn->Green/pIn->Alpha; + *pColors++ = pIn->Blue/pIn->Alpha; + *pColors++ = pIn->Alpha; + ++pIn; + } + return aRes; + } + + // XIntegerBitmapColorSpace + virtual ::sal_Int32 SAL_CALL getBitsPerPixel( ) override + { + return 32; + } + virtual uno::Sequence< ::sal_Int32 > SAL_CALL getComponentBitCounts( ) override + { + return maBitCounts; + } + virtual ::sal_Int8 SAL_CALL getEndianness( ) override + { + return util::Endianness::LITTLE; + } + virtual uno::Sequence SAL_CALL convertFromIntegerColorSpace( const uno::Sequence< ::sal_Int8 >& deviceColor, + const uno::Reference< rendering::XColorSpace >& targetColorSpace ) override + { + if( dynamic_cast(targetColorSpace.get()) ) + { + const sal_Int8* pIn( deviceColor.getConstArray() ); + const std::size_t nLen( deviceColor.getLength() ); + ENSURE_ARG_OR_THROW2(nLen%4==0, + "number of channels no multiple of 4", + static_cast(this), 0); + + uno::Sequence aRes(nLen); + double* pOut( aRes.getArray() ); + for( std::size_t i=0; i aIntermediate( + convertIntegerToARGB(deviceColor)); + return targetColorSpace->convertFromARGB(aIntermediate); + } + } + virtual uno::Sequence< ::sal_Int8 > SAL_CALL convertToIntegerColorSpace( const uno::Sequence< ::sal_Int8 >& deviceColor, + const uno::Reference< rendering::XIntegerBitmapColorSpace >& targetColorSpace ) override + { + if( dynamic_cast(targetColorSpace.get()) ) + { + // it's us, so simply pass-through the data + return deviceColor; + } + else + { + // TODO(P3): if we know anything about target + // colorspace, this can be greatly sped up + uno::Sequence aIntermediate( + convertIntegerToARGB(deviceColor)); + return targetColorSpace->convertIntegerFromARGB(aIntermediate); + } + } + virtual uno::Sequence< rendering::RGBColor > SAL_CALL convertIntegerToRGB( const uno::Sequence< ::sal_Int8 >& deviceColor ) override + { + const sal_Int8* pIn( deviceColor.getConstArray() ); + const std::size_t nLen( deviceColor.getLength() ); + ENSURE_ARG_OR_THROW2(nLen%4==0, + "number of channels no multiple of 4", + static_cast(this), 0); + + uno::Sequence< rendering::RGBColor > aRes(nLen/4); + rendering::RGBColor* pOut( aRes.getArray() ); + for( std::size_t i=0; i SAL_CALL convertIntegerToARGB( const uno::Sequence< ::sal_Int8 >& deviceColor ) override + { + const sal_Int8* pIn( deviceColor.getConstArray() ); + const std::size_t nLen( deviceColor.getLength() ); + ENSURE_ARG_OR_THROW2(nLen%4==0, + "number of channels no multiple of 4", + static_cast(this), 0); + + uno::Sequence< rendering::ARGBColor > aRes(nLen/4); + rendering::ARGBColor* pOut( aRes.getArray() ); + for( std::size_t i=0; i SAL_CALL convertIntegerToPARGB( const uno::Sequence< ::sal_Int8 >& deviceColor ) override + { + const sal_Int8* pIn( deviceColor.getConstArray() ); + const std::size_t nLen( deviceColor.getLength() ); + ENSURE_ARG_OR_THROW2(nLen%4==0, + "number of channels no multiple of 4", + static_cast(this), 0); + + uno::Sequence< rendering::ARGBColor > aRes(nLen/4); + rendering::ARGBColor* pOut( aRes.getArray() ); + for( std::size_t i=0; i SAL_CALL convertIntegerFromRGB( const uno::Sequence< rendering::RGBColor >& rgbColor ) override + { + const rendering::RGBColor* pIn( rgbColor.getConstArray() ); + const std::size_t nLen( rgbColor.getLength() ); + + uno::Sequence< sal_Int8 > aRes(nLen*4); + sal_Int8* pColors=aRes.getArray(); + for( std::size_t i=0; iRed); + *pColors++ = vcl::unotools::toByteColor(pIn->Green); + *pColors++ = vcl::unotools::toByteColor(pIn->Blue); + *pColors++ = 0; + ++pIn; + } + return aRes; + } + + virtual uno::Sequence< ::sal_Int8 > SAL_CALL convertIntegerFromARGB( const uno::Sequence< rendering::ARGBColor >& rgbColor ) override + { + const rendering::ARGBColor* pIn( rgbColor.getConstArray() ); + const std::size_t nLen( rgbColor.getLength() ); + + uno::Sequence< sal_Int8 > aRes(nLen*4); + sal_Int8* pColors=aRes.getArray(); + for( std::size_t i=0; iRed); + *pColors++ = vcl::unotools::toByteColor(pIn->Green); + *pColors++ = vcl::unotools::toByteColor(pIn->Blue); + *pColors++ = vcl::unotools::toByteColor(pIn->Alpha); + ++pIn; + } + return aRes; + } + + virtual uno::Sequence< ::sal_Int8 > SAL_CALL convertIntegerFromPARGB( const uno::Sequence< rendering::ARGBColor >& rgbColor ) override + { + const rendering::ARGBColor* pIn( rgbColor.getConstArray() ); + const std::size_t nLen( rgbColor.getLength() ); + + uno::Sequence< sal_Int8 > aRes(nLen*4); + sal_Int8* pColors=aRes.getArray(); + for( std::size_t i=0; iRed/pIn->Alpha); + *pColors++ = vcl::unotools::toByteColor(pIn->Green/pIn->Alpha); + *pColors++ = vcl::unotools::toByteColor(pIn->Blue/pIn->Alpha); + *pColors++ = vcl::unotools::toByteColor(pIn->Alpha); + ++pIn; + } + return aRes; + } + + public: + StandardColorSpace() : + maComponentTags(4), + maBitCounts(4) + { + sal_Int8* pTags = maComponentTags.getArray(); + sal_Int32* pBitCounts = maBitCounts.getArray(); + pTags[0] = rendering::ColorComponentTag::RGB_RED; + pTags[1] = rendering::ColorComponentTag::RGB_GREEN; + pTags[2] = rendering::ColorComponentTag::RGB_BLUE; + pTags[3] = rendering::ColorComponentTag::ALPHA; + + pBitCounts[0] = + pBitCounts[1] = + pBitCounts[2] = + pBitCounts[3] = 8; + } + }; + + class StandardNoAlphaColorSpace : public cppu::WeakImplHelper< css::rendering::XIntegerBitmapColorSpace > + { + private: + uno::Sequence< sal_Int8 > maComponentTags; + uno::Sequence< sal_Int32 > maBitCounts; + + virtual ::sal_Int8 SAL_CALL getType( ) override + { + return rendering::ColorSpaceType::RGB; + } + virtual uno::Sequence< ::sal_Int8 > SAL_CALL getComponentTags( ) override + { + return maComponentTags; + } + virtual ::sal_Int8 SAL_CALL getRenderingIntent( ) override + { + return rendering::RenderingIntent::PERCEPTUAL; + } + virtual uno::Sequence< beans::PropertyValue > SAL_CALL getProperties( ) override + { + return uno::Sequence< beans::PropertyValue >(); + } + virtual uno::Sequence< double > SAL_CALL convertColorSpace( const uno::Sequence< double >& deviceColor, + const uno::Reference< rendering::XColorSpace >& targetColorSpace ) override + { + // TODO(P3): if we know anything about target + // colorspace, this can be greatly sped up + uno::Sequence aIntermediate( + convertToARGB(deviceColor)); + return targetColorSpace->convertFromARGB(aIntermediate); + } + virtual uno::Sequence< rendering::RGBColor > SAL_CALL convertToRGB( const uno::Sequence< double >& deviceColor ) override + { + const double* pIn( deviceColor.getConstArray() ); + const std::size_t nLen( deviceColor.getLength() ); + ENSURE_ARG_OR_THROW2(nLen%4==0, + "number of channels no multiple of 4", + static_cast(this), 0); + + uno::Sequence< rendering::RGBColor > aRes(nLen/4); + rendering::RGBColor* pOut( aRes.getArray() ); + for( std::size_t i=0; i SAL_CALL convertToARGB( const uno::Sequence< double >& deviceColor ) override + { + const double* pIn( deviceColor.getConstArray() ); + const std::size_t nLen( deviceColor.getLength() ); + ENSURE_ARG_OR_THROW2(nLen%4==0, + "number of channels no multiple of 4", + static_cast(this), 0); + + uno::Sequence< rendering::ARGBColor > aRes(nLen/4); + rendering::ARGBColor* pOut( aRes.getArray() ); + for( std::size_t i=0; i SAL_CALL convertToPARGB( const uno::Sequence< double >& deviceColor ) override + { + const double* pIn( deviceColor.getConstArray() ); + const std::size_t nLen( deviceColor.getLength() ); + ENSURE_ARG_OR_THROW2(nLen%4==0, + "number of channels no multiple of 4", + static_cast(this), 0); + + uno::Sequence< rendering::ARGBColor > aRes(nLen/4); + rendering::ARGBColor* pOut( aRes.getArray() ); + for( std::size_t i=0; i SAL_CALL convertFromRGB( const uno::Sequence< rendering::RGBColor >& rgbColor ) override + { + const rendering::RGBColor* pIn( rgbColor.getConstArray() ); + const std::size_t nLen( rgbColor.getLength() ); + + uno::Sequence< double > aRes(nLen*4); + double* pColors=aRes.getArray(); + for( std::size_t i=0; iRed; + *pColors++ = pIn->Green; + *pColors++ = pIn->Blue; + *pColors++ = 1.0; // the value does not matter + ++pIn; + } + return aRes; + } + virtual uno::Sequence< double > SAL_CALL convertFromARGB( const uno::Sequence< rendering::ARGBColor >& rgbColor ) override + { + const rendering::ARGBColor* pIn( rgbColor.getConstArray() ); + const std::size_t nLen( rgbColor.getLength() ); + + uno::Sequence< double > aRes(nLen*4); + double* pColors=aRes.getArray(); + for( std::size_t i=0; iRed; + *pColors++ = pIn->Green; + *pColors++ = pIn->Blue; + *pColors++ = 1.0; // the value does not matter + ++pIn; + } + return aRes; + } + virtual uno::Sequence< double > SAL_CALL convertFromPARGB( const uno::Sequence< rendering::ARGBColor >& rgbColor ) override + { + const rendering::ARGBColor* pIn( rgbColor.getConstArray() ); + const std::size_t nLen( rgbColor.getLength() ); + + uno::Sequence< double > aRes(nLen*4); + double* pColors=aRes.getArray(); + for( std::size_t i=0; iRed/pIn->Alpha; + *pColors++ = pIn->Green/pIn->Alpha; + *pColors++ = pIn->Blue/pIn->Alpha; + *pColors++ = 1.0; // the value does not matter + ++pIn; + } + return aRes; + } + + // XIntegerBitmapColorSpace + virtual ::sal_Int32 SAL_CALL getBitsPerPixel( ) override + { + return 32; + } + virtual uno::Sequence< ::sal_Int32 > SAL_CALL getComponentBitCounts( ) override + { + return maBitCounts; + } + virtual ::sal_Int8 SAL_CALL getEndianness( ) override + { + return util::Endianness::LITTLE; + } + virtual uno::Sequence SAL_CALL convertFromIntegerColorSpace( const uno::Sequence< ::sal_Int8 >& deviceColor, + const uno::Reference< rendering::XColorSpace >& targetColorSpace ) override + { + if( dynamic_cast(targetColorSpace.get()) ) + { + const sal_Int8* pIn( deviceColor.getConstArray() ); + const std::size_t nLen( deviceColor.getLength() ); + ENSURE_ARG_OR_THROW2(nLen%4==0, + "number of channels no multiple of 4", + static_cast(this), 0); + + uno::Sequence aRes(nLen); + double* pOut( aRes.getArray() ); + for( std::size_t i=0; i aIntermediate( + convertIntegerToARGB(deviceColor)); + return targetColorSpace->convertFromARGB(aIntermediate); + } + } + virtual uno::Sequence< ::sal_Int8 > SAL_CALL convertToIntegerColorSpace( const uno::Sequence< ::sal_Int8 >& deviceColor, + const uno::Reference< rendering::XIntegerBitmapColorSpace >& targetColorSpace ) override + { + if( dynamic_cast(targetColorSpace.get()) ) + { + // it's us, so simply pass-through the data + return deviceColor; + } + else + { + // TODO(P3): if we know anything about target + // colorspace, this can be greatly sped up + uno::Sequence aIntermediate( + convertIntegerToARGB(deviceColor)); + return targetColorSpace->convertIntegerFromARGB(aIntermediate); + } + } + virtual uno::Sequence< rendering::RGBColor > SAL_CALL convertIntegerToRGB( const uno::Sequence< ::sal_Int8 >& deviceColor ) override + { + const sal_Int8* pIn( deviceColor.getConstArray() ); + const std::size_t nLen( deviceColor.getLength() ); + ENSURE_ARG_OR_THROW2(nLen%4==0, + "number of channels no multiple of 4", + static_cast(this), 0); + + uno::Sequence< rendering::RGBColor > aRes(nLen/4); + rendering::RGBColor* pOut( aRes.getArray() ); + for( std::size_t i=0; i SAL_CALL convertIntegerToARGB( const uno::Sequence< ::sal_Int8 >& deviceColor ) override + { + const sal_Int8* pIn( deviceColor.getConstArray() ); + const std::size_t nLen( deviceColor.getLength() ); + ENSURE_ARG_OR_THROW2(nLen%4==0, + "number of channels no multiple of 4", + static_cast(this), 0); + + uno::Sequence< rendering::ARGBColor > aRes(nLen/4); + rendering::ARGBColor* pOut( aRes.getArray() ); + for( std::size_t i=0; i SAL_CALL convertIntegerToPARGB( const uno::Sequence< ::sal_Int8 >& deviceColor ) override + { + const sal_Int8* pIn( deviceColor.getConstArray() ); + const std::size_t nLen( deviceColor.getLength() ); + ENSURE_ARG_OR_THROW2(nLen%4==0, + "number of channels no multiple of 4", + static_cast(this), 0); + + uno::Sequence< rendering::ARGBColor > aRes(nLen/4); + rendering::ARGBColor* pOut( aRes.getArray() ); + for( std::size_t i=0; i SAL_CALL convertIntegerFromRGB( const uno::Sequence< rendering::RGBColor >& rgbColor ) override + { + const rendering::RGBColor* pIn( rgbColor.getConstArray() ); + const std::size_t nLen( rgbColor.getLength() ); + + uno::Sequence< sal_Int8 > aRes(nLen*4); + sal_Int8* pColors=aRes.getArray(); + for( std::size_t i=0; iRed); + *pColors++ = vcl::unotools::toByteColor(pIn->Green); + *pColors++ = vcl::unotools::toByteColor(pIn->Blue); + *pColors++ = 1.0; + ++pIn; + } + return aRes; + } + + virtual uno::Sequence< ::sal_Int8 > SAL_CALL convertIntegerFromARGB( const uno::Sequence< rendering::ARGBColor >& rgbColor ) override + { + const rendering::ARGBColor* pIn( rgbColor.getConstArray() ); + const std::size_t nLen( rgbColor.getLength() ); + + uno::Sequence< sal_Int8 > aRes(nLen*4); + sal_Int8* pColors=aRes.getArray(); + for( std::size_t i=0; iRed); + *pColors++ = vcl::unotools::toByteColor(pIn->Green); + *pColors++ = vcl::unotools::toByteColor(pIn->Blue); + *pColors++ = -1; + ++pIn; + } + return aRes; + } + + virtual uno::Sequence< ::sal_Int8 > SAL_CALL convertIntegerFromPARGB( const uno::Sequence< rendering::ARGBColor >& rgbColor ) override + { + const rendering::ARGBColor* pIn( rgbColor.getConstArray() ); + const std::size_t nLen( rgbColor.getLength() ); + + uno::Sequence< sal_Int8 > aRes(nLen*4); + sal_Int8* pColors=aRes.getArray(); + for( std::size_t i=0; iRed/pIn->Alpha); + *pColors++ = vcl::unotools::toByteColor(pIn->Green/pIn->Alpha); + *pColors++ = vcl::unotools::toByteColor(pIn->Blue/pIn->Alpha); + *pColors++ = -1; + ++pIn; + } + return aRes; + } + + public: + StandardNoAlphaColorSpace() : + maComponentTags(3), + maBitCounts(3) + { + sal_Int8* pTags = maComponentTags.getArray(); + sal_Int32* pBitCounts = maBitCounts.getArray(); + pTags[0] = rendering::ColorComponentTag::RGB_RED; + pTags[1] = rendering::ColorComponentTag::RGB_GREEN; + pTags[2] = rendering::ColorComponentTag::RGB_BLUE; + + pBitCounts[0] = + pBitCounts[1] = + pBitCounts[2] = 8; + } + }; + + struct StandardColorSpaceHolder : public rtl::StaticWithInit, + StandardColorSpaceHolder> + { + uno::Reference operator()() + { + return new StandardColorSpace(); + } + }; + + struct StandardNoAlphaColorSpaceHolder : public rtl::StaticWithInit, + StandardNoAlphaColorSpaceHolder> + { + uno::Reference operator()() + { + return new StandardNoAlphaColorSpace(); + } + }; + } + + uno::Reference const & getStdColorSpace() + { + return StandardColorSpaceHolder::get(); + } + + uno::Reference const & getStdColorSpaceWithoutAlpha() + { + return StandardNoAlphaColorSpaceHolder::get(); + } + + rendering::IntegerBitmapLayout getStdMemoryLayout( const geometry::IntegerSize2D& rBmpSize ) + { + rendering::IntegerBitmapLayout aLayout; + + aLayout.ScanLines = rBmpSize.Height; + aLayout.ScanLineBytes = rBmpSize.Width*4; + aLayout.ScanLineStride = aLayout.ScanLineBytes; + aLayout.PlaneStride = 0; + aLayout.ColorSpace = getStdColorSpace(); + aLayout.Palette.clear(); + aLayout.IsMsbFirst = false; + + return aLayout; + } + + uno::Sequence colorToStdIntSequence( const ::Color& rColor ) + { + uno::Sequence aRet(4); + sal_Int8* pCols( aRet.getArray() ); +#ifdef OSL_BIGENDIAN + pCols[0] = rColor.GetRed(); + pCols[1] = rColor.GetGreen(); + pCols[2] = rColor.GetBlue(); + pCols[3] = 255-rColor.GetTransparency(); +#else + *reinterpret_cast(pCols) = sal_Int32(rColor); +#endif + return aRet; + } + + // Create a corrected view transformation out of the give one, + // which ensures that the rectangle given by (0,0) and + // rSpriteSize is mapped with its left,top corner to (0,0) + // again. This is required to properly render sprite + // animations to buffer bitmaps. + ::basegfx::B2DHomMatrix& calcRectToOriginTransform( ::basegfx::B2DHomMatrix& o_transform, + const ::basegfx::B2DRange& i_srcRect, + const ::basegfx::B2DHomMatrix& i_transformation ) + { + if( i_srcRect.isEmpty() ) + { + o_transform = i_transformation; + return o_transform; + } + + // transform by given transformation + ::basegfx::B2DRectangle aTransformedRect; + + calcTransformedRectBounds( aTransformedRect, + i_srcRect, + i_transformation ); + + // now move resulting left,top point of bounds to (0,0) + const basegfx::B2DHomMatrix aCorrectedTransform(basegfx::utils::createTranslateB2DHomMatrix( + -aTransformedRect.getMinX(), -aTransformedRect.getMinY())); + + // prepend to original transformation + o_transform = aCorrectedTransform * i_transformation; + + return o_transform; + } + + ::basegfx::B2DRange& calcTransformedRectBounds( ::basegfx::B2DRange& outRect, + const ::basegfx::B2DRange& inRect, + const ::basegfx::B2DHomMatrix& transformation ) + { + outRect.reset(); + + if( inRect.isEmpty() ) + return outRect; + + // transform all four extremal points of the rectangle, + // take bounding rect of those. + + // transform left-top point + outRect.expand( transformation * inRect.getMinimum() ); + + // transform bottom-right point + outRect.expand( transformation * inRect.getMaximum() ); + + ::basegfx::B2DPoint aPoint; + + // transform top-right point + aPoint.setX( inRect.getMaxX() ); + aPoint.setY( inRect.getMinY() ); + + aPoint *= transformation; + outRect.expand( aPoint ); + + // transform bottom-left point + aPoint.setX( inRect.getMinX() ); + aPoint.setY( inRect.getMaxY() ); + + aPoint *= transformation; + outRect.expand( aPoint ); + + // over and out. + return outRect; + } + + bool isInside( const ::basegfx::B2DRange& rContainedRect, + const ::basegfx::B2DRange& rTransformRect, + const ::basegfx::B2DHomMatrix& rTransformation ) + { + if( rContainedRect.isEmpty() || rTransformRect.isEmpty() ) + return false; + + ::basegfx::B2DPolygon aPoly( + ::basegfx::utils::createPolygonFromRect( rTransformRect ) ); + aPoly.transform( rTransformation ); + + return ::basegfx::utils::isInside( aPoly, + ::basegfx::utils::createPolygonFromRect( + rContainedRect ), + true ); + } + + namespace + { + bool clipAreaImpl( ::basegfx::B2IRange* o_pDestArea, + ::basegfx::B2IRange& io_rSourceArea, + ::basegfx::B2IPoint& io_rDestPoint, + const ::basegfx::B2IRange& rSourceBounds, + const ::basegfx::B2IRange& rDestBounds ) + { + const ::basegfx::B2IPoint aSourceTopLeft( + io_rSourceArea.getMinimum() ); + + ::basegfx::B2IRange aLocalSourceArea( io_rSourceArea ); + + // clip source area (which must be inside rSourceBounds) + aLocalSourceArea.intersect( rSourceBounds ); + + if( aLocalSourceArea.isEmpty() ) + return false; + + // calc relative new source area points (relative to orig + // source area) + const ::basegfx::B2IVector aUpperLeftOffset( + aLocalSourceArea.getMinimum()-aSourceTopLeft ); + const ::basegfx::B2IVector aLowerRightOffset( + aLocalSourceArea.getMaximum()-aSourceTopLeft ); + + ::basegfx::B2IRange aLocalDestArea( io_rDestPoint + aUpperLeftOffset, + io_rDestPoint + aLowerRightOffset ); + + // clip dest area (which must be inside rDestBounds) + aLocalDestArea.intersect( rDestBounds ); + + if( aLocalDestArea.isEmpty() ) + return false; + + // calc relative new dest area points (relative to orig + // source area) + const ::basegfx::B2IVector aDestUpperLeftOffset( + aLocalDestArea.getMinimum()-io_rDestPoint ); + const ::basegfx::B2IVector aDestLowerRightOffset( + aLocalDestArea.getMaximum()-io_rDestPoint ); + + io_rSourceArea = ::basegfx::B2IRange( aSourceTopLeft + aDestUpperLeftOffset, + aSourceTopLeft + aDestLowerRightOffset ); + io_rDestPoint = aLocalDestArea.getMinimum(); + + if( o_pDestArea ) + *o_pDestArea = aLocalDestArea; + + return true; + } + } + + bool clipScrollArea( ::basegfx::B2IRange& io_rSourceArea, + ::basegfx::B2IPoint& io_rDestPoint, + std::vector< ::basegfx::B2IRange >& o_ClippedAreas, + const ::basegfx::B2IRange& rBounds ) + { + ::basegfx::B2IRange aResultingDestArea; + + // compute full destination area (to determine uninitialized + // areas below) + const ::basegfx::B2I64Tuple& rRange( io_rSourceArea.getRange() ); + ::basegfx::B2IRange aInputDestArea( io_rDestPoint.getX(), + io_rDestPoint.getY(), + (io_rDestPoint.getX() + + static_cast(rRange.getX())), + (io_rDestPoint.getY() + + static_cast(rRange.getY())) ); + // limit to output area (no point updating outside of it) + aInputDestArea.intersect( rBounds ); + + // clip to rBounds + if( !clipAreaImpl( &aResultingDestArea, + io_rSourceArea, + io_rDestPoint, + rBounds, + rBounds ) ) + return false; + + // finally, compute all areas clipped off the total + // destination area. + ::basegfx::computeSetDifference( o_ClippedAreas, + aInputDestArea, + aResultingDestArea ); + + return true; + } + + ::basegfx::B2IRange spritePixelAreaFromB2DRange( const ::basegfx::B2DRange& rRange ) + { + if( rRange.isEmpty() ) + return ::basegfx::B2IRange(); + + const ::basegfx::B2IPoint aTopLeft( ::basegfx::fround( rRange.getMinX() ), + ::basegfx::fround( rRange.getMinY() ) ); + return ::basegfx::B2IRange( aTopLeft, + aTopLeft + ::basegfx::B2IPoint( + ::basegfx::fround( rRange.getWidth() ), + ::basegfx::fround( rRange.getHeight() ) ) ); + } + + uno::Sequence< uno::Any >& getDeviceInfo( const uno::Reference< rendering::XCanvas >& i_rxCanvas, + uno::Sequence< uno::Any >& o_rxParams ) + { + o_rxParams.realloc( 0 ); + + if( i_rxCanvas.is() ) + { + try + { + uno::Reference< rendering::XGraphicDevice > xDevice( i_rxCanvas->getDevice(), + uno::UNO_SET_THROW ); + + uno::Reference< lang::XServiceInfo > xServiceInfo( xDevice, + uno::UNO_QUERY_THROW ); + uno::Reference< beans::XPropertySet > xPropSet( xDevice, + uno::UNO_QUERY_THROW ); + + o_rxParams.realloc( 2 ); + + o_rxParams[ 0 ] <<= xServiceInfo->getImplementationName(); + o_rxParams[ 1 ] = xPropSet->getPropertyValue( "DeviceHandle" ); + } + catch( const uno::Exception& ) + { + // ignore, but return empty sequence + } + } + + return o_rxParams; + } + + awt::Rectangle getAbsoluteWindowRect( const awt::Rectangle& rRect, + const uno::Reference< awt::XWindow2 >& xWin ) + { + awt::Rectangle aRetVal( rRect ); + + VclPtr pWindow = VCLUnoHelper::GetWindow(xWin); + if( pWindow ) + { + ::Point aPoint( aRetVal.X, + aRetVal.Y ); + + aPoint = pWindow->OutputToScreenPixel( aPoint ); + + aRetVal.X = aPoint.X(); + aRetVal.Y = aPoint.Y(); + } + + return aRetVal; + } + + ::basegfx::B2DPolyPolygon getBoundMarksPolyPolygon( const ::basegfx::B2DRange& rRange ) + { + ::basegfx::B2DPolyPolygon aPolyPoly; + ::basegfx::B2DPolygon aPoly; + + const double nX0( rRange.getMinX() ); + const double nY0( rRange.getMinY() ); + const double nX1( rRange.getMaxX() ); + const double nY1( rRange.getMaxY() ); + + aPoly.append( ::basegfx::B2DPoint( nX0+4, + nY0 ) ); + aPoly.append( ::basegfx::B2DPoint( nX0, + nY0 ) ); + aPoly.append( ::basegfx::B2DPoint( nX0, + nY0+4 ) ); + aPolyPoly.append( aPoly ); aPoly.clear(); + + aPoly.append( ::basegfx::B2DPoint( nX1-4, + nY0 ) ); + aPoly.append( ::basegfx::B2DPoint( nX1, + nY0 ) ); + aPoly.append( ::basegfx::B2DPoint( nX1, + nY0+4 ) ); + aPolyPoly.append( aPoly ); aPoly.clear(); + + aPoly.append( ::basegfx::B2DPoint( nX0+4, + nY1 ) ); + aPoly.append( ::basegfx::B2DPoint( nX0, + nY1 ) ); + aPoly.append( ::basegfx::B2DPoint( nX0, + nY1-4 ) ); + aPolyPoly.append( aPoly ); aPoly.clear(); + + aPoly.append( ::basegfx::B2DPoint( nX1-4, + nY1 ) ); + aPoly.append( ::basegfx::B2DPoint( nX1, + nY1 ) ); + aPoly.append( ::basegfx::B2DPoint( nX1, + nY1-4 ) ); + aPolyPoly.append( aPoly ); + + return aPolyPoly; + } + + int calcGradientStepCount( ::basegfx::B2DHomMatrix& rTotalTransform, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState, + const rendering::Texture& texture, + int nColorSteps ) + { + // calculate overall texture transformation (directly from + // texture to device space). + ::basegfx::B2DHomMatrix aMatrix; + + rTotalTransform.identity(); + ::basegfx::unotools::homMatrixFromAffineMatrix( rTotalTransform, + texture.AffineTransform ); + ::canvas::tools::mergeViewAndRenderTransform(aMatrix, + viewState, + renderState); + rTotalTransform *= aMatrix; // prepend total view/render transformation + + // determine size of gradient in device coordinate system + // (to e.g. determine sensible number of gradient steps) + ::basegfx::B2DPoint aLeftTop( 0.0, 0.0 ); + ::basegfx::B2DPoint aLeftBottom( 0.0, 1.0 ); + ::basegfx::B2DPoint aRightTop( 1.0, 0.0 ); + ::basegfx::B2DPoint aRightBottom( 1.0, 1.0 ); + + aLeftTop *= rTotalTransform; + aLeftBottom *= rTotalTransform; + aRightTop *= rTotalTransform; + aRightBottom*= rTotalTransform; + + // longest line in gradient bound rect + const int nGradientSize( + static_cast( + std::max( + ::basegfx::B2DVector(aRightBottom-aLeftTop).getLength(), + ::basegfx::B2DVector(aRightTop-aLeftBottom).getLength() ) + 1.0 ) ); + + // typical number for pixel of the same color (strip size) + const int nStripSize( nGradientSize < 50 ? 2 : 4 ); + + // use at least three steps, and at utmost the number of color + // steps + return std::max( 3, + std::min( + nGradientSize / nStripSize, + nColorSteps ) ); + } + + void clipOutDev(const rendering::ViewState& viewState, + const rendering::RenderState& renderState, + OutputDevice& rOutDev, + OutputDevice* p2ndOutDev) + { + // accumulate non-empty clips into one region + vcl::Region aClipRegion(true); + + if( viewState.Clip.is() ) + { + ::basegfx::B2DPolyPolygon aClipPoly( + ::basegfx::unotools::b2DPolyPolygonFromXPolyPolygon2D(viewState.Clip) ); + + if( aClipPoly.count() ) + { + // setup non-empty clipping + ::basegfx::B2DHomMatrix aMatrix; + aClipPoly.transform( + ::basegfx::unotools::homMatrixFromAffineMatrix( aMatrix, + viewState.AffineTransform ) ); + + aClipRegion = vcl::Region::GetRegionFromPolyPolygon( ::tools::PolyPolygon( aClipPoly ) ); + } + else + { + // clip polygon is empty + aClipRegion.SetEmpty(); + } + } + + if( renderState.Clip.is() ) + { + ::basegfx::B2DPolyPolygon aClipPoly( + ::basegfx::unotools::b2DPolyPolygonFromXPolyPolygon2D(renderState.Clip) ); + + ::basegfx::B2DHomMatrix aMatrix; + aClipPoly.transform( + ::canvas::tools::mergeViewAndRenderTransform( aMatrix, + viewState, + renderState ) ); + + if( aClipPoly.count() ) + { + // setup non-empty clipping + vcl::Region aRegion = vcl::Region::GetRegionFromPolyPolygon( ::tools::PolyPolygon( aClipPoly ) ); + aClipRegion.Intersect( aRegion ); + } + else + { + // clip polygon is empty + aClipRegion.SetEmpty(); + } + } + + // setup accumulated clip region. Note that setting an + // empty clip region denotes "clip everything" on the + // OutputDevice (which is why we translate that into + // SetClipRegion() here). When both view and render clip + // are empty, aClipRegion remains default-constructed, + // i.e. empty, too. + if( aClipRegion.IsNull() ) + { + rOutDev.SetClipRegion(); + + if( p2ndOutDev ) + p2ndOutDev->SetClipRegion(); + } + else + { + rOutDev.SetClipRegion( aClipRegion ); + + if( p2ndOutDev ) + p2ndOutDev->SetClipRegion( aClipRegion ); + } + } + + void extractExtraFontProperties(const uno::Sequence& rExtraFontProperties, + sal_uInt32 &rEmphasisMark) + { + for(const beans::PropertyValue& rPropVal : rExtraFontProperties) + { + if (rPropVal.Name == "EmphasisMark") + rPropVal.Value >>= rEmphasisMark; + } + } + +} // namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/tools/elapsedtime.cxx b/canvas/source/tools/elapsedtime.cxx new file mode 100644 index 000000000..84a6ed5c4 --- /dev/null +++ b/canvas/source/tools/elapsedtime.cxx @@ -0,0 +1,129 @@ +/* -*- 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 + +namespace canvas::tools { + +double ElapsedTime::getSystemTime() +{ + return ::tools::Time::GetMonotonicTicks() / 1.0E6; +} + +ElapsedTime::ElapsedTime() + : m_pTimeBase(), + m_fLastQueriedTime( 0.0 ), + m_fStartTime( getSystemTime() ), + m_fFrozenTime( 0.0 ), + m_bInPauseMode( false ), + m_bInHoldMode( false ) +{ +} + +ElapsedTime::ElapsedTime( + std::shared_ptr const & pTimeBase ) + : m_pTimeBase( pTimeBase ), + m_fLastQueriedTime( 0.0 ), + m_fStartTime( getCurrentTime() ), + m_fFrozenTime( 0.0 ), + m_bInPauseMode( false ), + m_bInHoldMode( false ) +{ +} + +void ElapsedTime::reset() +{ + m_fLastQueriedTime = 0.0; + m_fStartTime = getCurrentTime(); + m_fFrozenTime = 0.0; + m_bInPauseMode = false; + m_bInHoldMode = false; +} + +void ElapsedTime::adjustTimer( double fOffset ) +{ + // to make getElapsedTime() become _larger_, have to reduce + // m_fStartTime. + m_fStartTime -= fOffset; + + // also adjust frozen time, this method must _always_ affect the + // value returned by getElapsedTime()! + if (m_bInHoldMode || m_bInPauseMode) + m_fFrozenTime += fOffset; +} + +double ElapsedTime::getCurrentTime() const +{ + return m_pTimeBase == nullptr ? getSystemTime() : m_pTimeBase->getElapsedTimeImpl(); +} + +double ElapsedTime::getElapsedTime() const +{ + m_fLastQueriedTime = getElapsedTimeImpl(); + return m_fLastQueriedTime; +} + +double ElapsedTime::getElapsedTimeImpl() const +{ + if (m_bInHoldMode || m_bInPauseMode) + return m_fFrozenTime; + + return getCurrentTime() - m_fStartTime; +} + +void ElapsedTime::pauseTimer() +{ + m_fFrozenTime = getElapsedTimeImpl(); + m_bInPauseMode = true; +} + +void ElapsedTime::continueTimer() +{ + m_bInPauseMode = false; + + // stop pausing, time runs again. Note that + // getElapsedTimeImpl() honors hold mode, i.e. a + // continueTimer() in hold mode will preserve the latter + const double fPauseDuration( getElapsedTimeImpl() - m_fFrozenTime ); + + // adjust start time, such that subsequent getElapsedTime() calls + // will virtually start from m_fFrozenTime. + m_fStartTime += fPauseDuration; +} + +void ElapsedTime::holdTimer() +{ + // when called during hold mode (e.g. more than once per time + // object), the original hold time will be maintained. + m_fFrozenTime = getElapsedTimeImpl(); + m_bInHoldMode = true; +} + +void ElapsedTime::releaseTimer() +{ + m_bInHoldMode = false; +} + +} // namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/tools/page.cxx b/canvas/source/tools/page.cxx new file mode 100644 index 000000000..3537fa0b6 --- /dev/null +++ b/canvas/source/tools/page.cxx @@ -0,0 +1,135 @@ +/* -*- 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 "page.hxx" + +namespace canvas +{ + Page::Page( const std::shared_ptr &rRenderModule ) : + mpRenderModule(rRenderModule), + mpSurface(rRenderModule->createSurface(::basegfx::B2ISize())) + { + } + + void Page::validate() + { + if(!(isValid())) + { + for( const auto& rFragmentPtr : mpFragments ) + rFragmentPtr->refresh(); + } + } + + bool Page::isValid() const + { + return mpSurface && mpSurface->isValid(); + } + + FragmentSharedPtr Page::allocateSpace( const ::basegfx::B2ISize& rSize ) + { + SurfaceRect rect(rSize); + if(insert(rect)) + { + FragmentSharedPtr pFragment = std::make_shared(rect,this); + mpFragments.push_back(pFragment); + return pFragment; + } + + return FragmentSharedPtr(); + } + + bool Page::nakedFragment( const FragmentSharedPtr& pFragment ) + { + SurfaceRect rect(pFragment->getSize()); + if(insert(rect)) + { + pFragment->setPage(this); + mpFragments.push_back(pFragment); + return true; + } + + return false; + } + + void Page::free( const FragmentSharedPtr& pFragment ) + { + // the fragment passes as argument is no longer + // dedicated to this page. either it is about to + // be relocated to some other page or it will + // currently be deleted. in either case, simply + // remove the reference from our internal storage. + FragmentContainer_t::iterator it( + std::remove( + mpFragments.begin(),mpFragments.end(),pFragment)); + mpFragments.erase(it,mpFragments.end()); + } + + bool Page::insert( SurfaceRect& r ) + { + for( const auto& pFragment : mpFragments ) + { + const SurfaceRect &rect = pFragment->getRect(); + const sal_Int32 x = rect.maPos.getX(); + const sal_Int32 y = rect.maPos.getY(); + // to avoid interpolation artifacts from other textures, + // one pixel gap between them + const sal_Int32 w = rect.maSize.getX()+1; + const sal_Int32 h = rect.maSize.getY()+1; + + // probe location to the right + r.maPos.setX(x+w); + r.maPos.setY(y); + if(isValidLocation(r)) + return true; + + // probe location at bottom + r.maPos.setX(x); + r.maPos.setY(y+h); + if(isValidLocation(r)) + return true; + } + + r.maPos.setX(0); + r.maPos.setY(0); + + return isValidLocation(r); + } + + bool Page::isValidLocation( const SurfaceRect& r ) const + { + // the rectangle passed as argument has a valid + // location if and only if there's no intersection + // with existing areas. + SurfaceRect aBoundary(mpRenderModule->getPageSize()); + if( !r.inside(aBoundary) ) + return false; + + for( const auto& pFragment : mpFragments ) + { + if( r.intersection( pFragment->getRect() ) ) + return false; + } + + return true; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/tools/page.hxx b/canvas/source/tools/page.hxx new file mode 100644 index 000000000..cfb3cbd9d --- /dev/null +++ b/canvas/source/tools/page.hxx @@ -0,0 +1,150 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_CANVAS_SOURCE_TOOLS_PAGE_HXX +#define INCLUDED_CANVAS_SOURCE_TOOLS_PAGE_HXX + +#include +#include +#include +#include +#include + +#include +#include +#include "surfacerect.hxx" + +namespace canvas +{ + class PageFragment; + + typedef std::shared_ptr< PageFragment > FragmentSharedPtr; + + /** One page of IRenderModule-provided texture space + */ + class Page + { + public: + explicit Page( const std::shared_ptr& rRenderModule ); + + FragmentSharedPtr allocateSpace( const ::basegfx::B2ISize& rSize ); + bool nakedFragment( const FragmentSharedPtr& pFragment ); + void free( const FragmentSharedPtr& pFragment ); + const std::shared_ptr& getSurface() const { return mpSurface; } + bool isValid() const; + void validate(); + + private: + typedef std::vector FragmentContainer_t; + + std::shared_ptr mpRenderModule; + std::shared_ptr mpSurface; + FragmentContainer_t mpFragments; + + bool insert( SurfaceRect& r ); + bool isValidLocation( const SurfaceRect& r ) const; + }; + + typedef std::shared_ptr< Page > PageSharedPtr; + + + /** A part of a page, which gets allocated to a surface + */ + class PageFragment + { + public: + PageFragment( const SurfaceRect& r, + Page* pPage ) : + mpPage(pPage), + maRect(r), + mpBuffer(), + maSourceOffset() + { + } + + /// Creates a 'naked' fragment. + explicit PageFragment( const ::basegfx::B2ISize& rSize ) : + mpPage(nullptr), + maRect(rSize), + mpBuffer(), + maSourceOffset() + { + } + + bool isNaked() const { return (mpPage == nullptr); } + const SurfaceRect& getRect() const { return maRect; } + const ::basegfx::B2IPoint& getPos() const { return maRect.maPos; } + const ::basegfx::B2ISize& getSize() const { return maRect.maSize; } + void setColorBuffer( const std::shared_ptr& pColorBuffer ) { mpBuffer=pColorBuffer; } + void setSourceOffset( const ::basegfx::B2IPoint& rOffset ) { maSourceOffset=rOffset; } + void setPage( Page* pPage ) { mpPage=pPage; } + + void free( const FragmentSharedPtr& pFragment ) + { + if(mpPage) + mpPage->free(pFragment); + + mpPage=nullptr; + } + + bool select( bool bRefresh ) + { + // request was made to select this fragment, + // but this fragment has not been located on any + // of the available pages, we need to hurry now. + if(!mpPage) + return false; + + std::shared_ptr pSurface(mpPage->getSurface()); + + // select this surface before wiping the contents + // since a specific implementation could trigger + // a rendering operation here... + if(!(pSurface->selectTexture())) + return false; + + // call refresh() if requested, otherwise we're up to date... + return !bRefresh || refresh(); + } + + bool refresh() + { + if(!mpPage) + return false; + + std::shared_ptr pSurface(mpPage->getSurface()); + + return pSurface->update( maRect.maPos, + ::basegfx::B2IRectangle( + maSourceOffset, + maSourceOffset + maRect.maSize ), + *mpBuffer ); + } + + private: + Page* mpPage; + SurfaceRect maRect; + std::shared_ptr mpBuffer; + ::basegfx::B2IPoint maSourceOffset; + }; +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/tools/pagemanager.cxx b/canvas/source/tools/pagemanager.cxx new file mode 100644 index 000000000..c1e874ca4 --- /dev/null +++ b/canvas/source/tools/pagemanager.cxx @@ -0,0 +1,152 @@ +/* -*- 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 "pagemanager.hxx" + +namespace canvas +{ + FragmentSharedPtr PageManager::allocateSpace( const ::basegfx::B2ISize& rSize ) + { + // we are asked to find a location for the requested size. + // first we try to satisfy the request from the + // remaining space in the existing pages. + for( const auto& pPage : maPages ) + { + FragmentSharedPtr pFragment( pPage->allocateSpace(rSize) ); + if(pFragment) + { + // the page created a new fragment, since we maybe want + // to consolidate sparse pages we keep a reference to + // the fragment. + maFragments.push_back(pFragment); + return pFragment; + } + } + + // otherwise try to create a new page and allocate space there... + PageSharedPtr pPage = std::make_shared(mpRenderModule); + if(pPage->isValid()) + { + maPages.push_back(pPage); + FragmentSharedPtr pFragment(pPage->allocateSpace(rSize)); + if (pFragment) + maFragments.push_back(pFragment); + return pFragment; + } + + // the rendermodule failed to create a new page [maybe out + // of videomemory], and all other pages could not take + // the new request. we decide to create a 'naked' fragment + // which will receive its location later. + FragmentSharedPtr pFragment = std::make_shared(rSize); + maFragments.push_back(pFragment); + return pFragment; + } + + void PageManager::free( const FragmentSharedPtr& pFragment ) + { + // erase the reference to the given fragment from our + // internal container. + FragmentContainer_t::iterator it( + std::remove( + maFragments.begin(),maFragments.end(),pFragment)); + maFragments.erase(it,maFragments.end()); + + // let the fragment itself know about it... + // we need to pass 'this' as argument since the fragment + // needs to pass this to the page and can't create + // shared_ptr from itself... + pFragment->free(pFragment); + } + + void PageManager::nakedFragment( const FragmentSharedPtr& pFragment ) + { + if(maPages.empty()) + return; + + // okay, one last chance is left, we try all available + // pages again. maybe some other fragment was deleted + // and we can exploit the space. + while( !( relocate( pFragment ) ) ) + { + // no way, we need to free up some space... + // TODO(F1): this is a heuristic, could + // be designed as a policy. + auto aEnd( maFragments.cend() ); + auto aCurrMax( aEnd ); + sal_uInt32 nCurrMaxArea = 0; + for( auto aCurr = maFragments.begin(); aCurr != aEnd; ++aCurr ) + { + if( *aCurr && !( ( *aCurr )->isNaked() ) ) + { + const ::basegfx::B2ISize& rSize( ( *aCurr )->getSize() ); + sal_uInt32 nArea( rSize.getX() * rSize.getY() ); + + if( nCurrMaxArea < nArea ) + { + aCurrMax = aCurr; + nCurrMaxArea = nArea; + } + } + } + // this does not erase the candidate, + // but makes it 'naked'... + if( aCurrMax != aEnd ) + ( *aCurrMax )->free( *aCurrMax ); + else + break; + } + } + + bool PageManager::relocate( const FragmentSharedPtr& pFragment ) + { + // the fragment passed as argument is assumed to + // be naked, that is it is not located on any page. + // we try all available pages again, maybe some + // other fragment was deleted and we can exploit the space. + for( const auto& pPage : maPages ) + { + // if the page at hand takes the fragment, we immediately + // call select() to pull the information from the associated + // image to the hardware surface. + if( pPage->nakedFragment( pFragment ) ) + { + // dirty, since newly allocated. + pFragment->select(true); + return true; + } + } + return false; + } + + void PageManager::validatePages() + { + for( const auto& rPagePtr : maPages ) + rPagePtr->validate(); + } + + ::basegfx::B2ISize PageManager::getPageSize() const + { + return mpRenderModule->getPageSize(); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/tools/pagemanager.hxx b/canvas/source/tools/pagemanager.hxx new file mode 100644 index 000000000..761c84c33 --- /dev/null +++ b/canvas/source/tools/pagemanager.hxx @@ -0,0 +1,78 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_CANVAS_SOURCE_TOOLS_PAGEMANAGER_HXX +#define INCLUDED_CANVAS_SOURCE_TOOLS_PAGEMANAGER_HXX + +#include +#include + +#include "page.hxx" + +namespace canvas +{ + // PageManager + class PageManager + { + public: + explicit PageManager(const std::shared_ptr& rRenderModule) + : mpRenderModule(rRenderModule) + { + } + + // returns the maximum size of a hardware + // accelerated page, e.g. OpenGL texture. + ::basegfx::B2ISize getPageSize() const; + + const std::shared_ptr& getRenderModule() const { return mpRenderModule; } + + FragmentSharedPtr allocateSpace( const ::basegfx::B2ISize& rSize ); + void free( const FragmentSharedPtr& pFragment ); + + void nakedFragment( const FragmentSharedPtr& pFragment ); + + void validatePages(); + + private: + // the pagemanager needs access to the rendermodule + // since we query for system resources from it. + std::shared_ptr mpRenderModule; + + // here we collect all fragments that will be created + // since we need them for relocation purposes. + typedef std::vector FragmentContainer_t; + FragmentContainer_t maFragments; + + // this is the container holding all created pages, + // behind the scenes these are real hardware surfaces. + std::vector maPages; + + bool relocate( const FragmentSharedPtr& pFragment ); + }; + + + // PageManagerSharedPtr + + + typedef std::shared_ptr< PageManager > PageManagerSharedPtr; +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/tools/parametricpolypolygon.cxx b/canvas/source/tools/parametricpolypolygon.cxx new file mode 100644 index 000000000..a0a9a7880 --- /dev/null +++ b/canvas/source/tools/parametricpolypolygon.cxx @@ -0,0 +1,245 @@ +/* -*- 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 + +#include + +using namespace ::com::sun::star; + +namespace canvas +{ + uno::Sequence ParametricPolyPolygon::getAvailableServiceNames() + { + return {"LinearGradient", + "EllipticalGradient", + "RectangularGradient"}; + } + + ParametricPolyPolygon* ParametricPolyPolygon::create( + const uno::Reference< rendering::XGraphicDevice >& rDevice, + const OUString& rServiceName, + const uno::Sequence< uno::Any >& rArgs ) + { + uno::Sequence< uno::Sequence< double > > colorSequence(2); + uno::Sequence< double > colorStops(2); + double fAspectRatio=1.0; + + // defaults + uno::Sequence< rendering::RGBColor > rgbColors(1); + rgbColors[0] = rendering::RGBColor(0,0,0); + colorSequence[0] = rDevice->getDeviceColorSpace()->convertFromRGB(rgbColors); + rgbColors[0] = rendering::RGBColor(1,1,1); + colorSequence[1] = rDevice->getDeviceColorSpace()->convertFromRGB(rgbColors); + colorStops[0] = 0; + colorStops[1] = 1; + + // extract args + for( const uno::Any& rArg : rArgs ) + { + beans::PropertyValue aProp; + if( rArg >>= aProp ) + { + if ( aProp.Name == "Colors" ) + { + aProp.Value >>= colorSequence; + } + else if ( aProp.Name == "Stops" ) + { + aProp.Value >>= colorStops; + } + else if ( aProp.Name == "AspectRatio" ) + { + aProp.Value >>= fAspectRatio; + } + } + } + + if ( rServiceName == "LinearGradient" ) + { + return createLinearHorizontalGradient(rDevice, colorSequence, colorStops); + } + else if ( rServiceName == "EllipticalGradient" ) + { + return createEllipticalGradient(rDevice, colorSequence, colorStops, fAspectRatio); + } + else if ( rServiceName == "RectangularGradient" ) + { + return createRectangularGradient(rDevice, colorSequence, colorStops, fAspectRatio); + } + else if ( rServiceName == "VerticalLineHatch" ) + { + // TODO: NYI + } + else if ( rServiceName == "OrthogonalLinesHatch" ) + { + // TODO: NYI + } + else if ( rServiceName == "ThreeCrossingLinesHatch" ) + { + // TODO: NYI + } + else if ( rServiceName == "FourCrossingLinesHatch" ) + { + // TODO: NYI + } + + return nullptr; + } + + ParametricPolyPolygon* ParametricPolyPolygon::createLinearHorizontalGradient( + const uno::Reference< rendering::XGraphicDevice >& rDevice, + const uno::Sequence< uno::Sequence< double > >& colors, + const uno::Sequence< double >& stops ) + { + // TODO(P2): hold gradient brush statically, and only setup + // the colors + return new ParametricPolyPolygon( rDevice, GradientType::Linear, colors, stops ); + } + + ParametricPolyPolygon* ParametricPolyPolygon::createEllipticalGradient( + const uno::Reference< rendering::XGraphicDevice >& rDevice, + const uno::Sequence< uno::Sequence< double > >& colors, + const uno::Sequence< double >& stops, + double fAspectRatio ) + { + // TODO(P2): hold gradient polygon statically, and only setup + // the colors + return new ParametricPolyPolygon( + rDevice, + ::basegfx::utils::createPolygonFromCircle( + ::basegfx::B2DPoint(0,0), 1 ), + GradientType::Elliptical, + colors, stops, fAspectRatio ); + } + + ParametricPolyPolygon* ParametricPolyPolygon::createRectangularGradient( const uno::Reference< rendering::XGraphicDevice >& rDevice, + const uno::Sequence< uno::Sequence< double > >& colors, + const uno::Sequence< double >& stops, + double fAspectRatio ) + { + // TODO(P2): hold gradient polygon statically, and only setup + // the colors + return new ParametricPolyPolygon( + rDevice, + ::basegfx::utils::createPolygonFromRect( + ::basegfx::B2DRectangle( -1, -1, 1, 1 ) ), + GradientType::Rectangular, + colors, stops, fAspectRatio ); + } + + void SAL_CALL ParametricPolyPolygon::disposing() + { + ::osl::MutexGuard aGuard( m_aMutex ); + + mxDevice.clear(); + } + + uno::Reference< rendering::XPolyPolygon2D > SAL_CALL ParametricPolyPolygon::getOutline( double /*t*/ ) + { + // TODO(F1): outline NYI + return uno::Reference< rendering::XPolyPolygon2D >(); + } + + uno::Sequence< double > SAL_CALL ParametricPolyPolygon::getColor( double /*t*/ ) + { + // TODO(F1): color NYI + return uno::Sequence< double >(); + } + + uno::Sequence< double > SAL_CALL ParametricPolyPolygon::getPointColor( const geometry::RealPoint2D& /*point*/ ) + { + // TODO(F1): point color NYI + return uno::Sequence< double >(); + } + + uno::Reference< rendering::XColorSpace > SAL_CALL ParametricPolyPolygon::getColorSpace() + { + ::osl::MutexGuard aGuard( m_aMutex ); + + return mxDevice.is() ? mxDevice->getDeviceColorSpace() : uno::Reference< rendering::XColorSpace >(); + } + + + OUString SAL_CALL ParametricPolyPolygon::getImplementationName( ) + { + return "Canvas::ParametricPolyPolygon"; + } + + sal_Bool SAL_CALL ParametricPolyPolygon::supportsService( const OUString& ServiceName ) + { + return cppu::supportsService(this, ServiceName); + } + + uno::Sequence< OUString > SAL_CALL ParametricPolyPolygon::getSupportedServiceNames( ) + { + return { "com.sun.star.rendering.ParametricPolyPolygon" }; + } + + ParametricPolyPolygon::~ParametricPolyPolygon() + { + } + + ParametricPolyPolygon::ParametricPolyPolygon( const uno::Reference< rendering::XGraphicDevice >& rDevice, + const ::basegfx::B2DPolygon& rGradientPoly, + GradientType eType, + const uno::Sequence< uno::Sequence< double > >& rColors, + const uno::Sequence< double >& rStops, + double nAspectRatio ) : + ParametricPolyPolygon_Base( m_aMutex ), + mxDevice( rDevice ), + maValues( rGradientPoly, + rColors, + rStops, + nAspectRatio, + eType ) + { + } + + ParametricPolyPolygon::ParametricPolyPolygon( const uno::Reference< rendering::XGraphicDevice >& rDevice, + GradientType eType, + const uno::Sequence< uno::Sequence< double > >& rColors, + const uno::Sequence< double >& rStops ) : + ParametricPolyPolygon_Base( m_aMutex ), + mxDevice( rDevice ), + maValues( ::basegfx::B2DPolygon(), + rColors, + rStops, + 1.0, + eType ) + { + } + + ParametricPolyPolygon::Values ParametricPolyPolygon::getValues() const + { + ::osl::MutexGuard aGuard( m_aMutex ); + + return maValues; + } + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/tools/propertysethelper.cxx b/canvas/source/tools/propertysethelper.cxx new file mode 100644 index 000000000..e7880fe4b --- /dev/null +++ b/canvas/source/tools/propertysethelper.cxx @@ -0,0 +1,156 @@ +/* -*- 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 + +using namespace ::com::sun::star; + +namespace canvas +{ + namespace + { + void throwUnknown( const OUString& aPropertyName ) + { + throw beans::UnknownPropertyException( + "PropertySetHelper: property " + + aPropertyName + " not found." + ); + } + + void throwVeto( const OUString& aPropertyName ) + { + throw beans::PropertyVetoException( + "PropertySetHelper: property " + + aPropertyName + " access was vetoed." ); + } + + struct EntryComparator + { + bool operator()( const PropertySetHelper::MapType::MapEntry& rLHS, + const PropertySetHelper::MapType::MapEntry& rRHS ) + { + return strcmp( rLHS.maKey, + rRHS.maKey ) < 0; + } + }; + } + + PropertySetHelper::PropertySetHelper() : + mpMap(), + maMapEntries() + { + } + + void PropertySetHelper::initProperties( const InputMap& rMap ) + { + mpMap.reset(); + maMapEntries = rMap; + + std::sort( maMapEntries.begin(), + maMapEntries.end(), + EntryComparator() ); + + if( !maMapEntries.empty() ) + mpMap.reset( new MapType(maMapEntries.data(), + maMapEntries.size(), + true) ); + } + + void PropertySetHelper::addProperties( const InputMap& rMap ) + { + InputMap aMerged( maMapEntries ); + aMerged.insert( aMerged.end(), + rMap.begin(), + rMap.end() ); + + initProperties( aMerged ); + } + + bool PropertySetHelper::isPropertyName( const OUString& aPropertyName ) const + { + if (!mpMap) + return false; + + Callbacks aDummy; + return mpMap->lookup( aPropertyName, + aDummy ); + } + + uno::Reference< beans::XPropertySetInfo > PropertySetHelper::getPropertySetInfo() const + { + // we're a stealth property set + return uno::Reference< beans::XPropertySetInfo >(); + } + + void PropertySetHelper::setPropertyValue( const OUString& aPropertyName, + const uno::Any& aValue ) + { + Callbacks aCallbacks; + if (!mpMap || !mpMap->lookup(aPropertyName, aCallbacks)) + { + throwUnknown( aPropertyName ); + } + + if (!aCallbacks.setter) + throwVeto( aPropertyName ); + + aCallbacks.setter(aValue); + } + + uno::Any PropertySetHelper::getPropertyValue( const OUString& aPropertyName ) const + { + Callbacks aCallbacks; + if (!mpMap || !mpMap->lookup(aPropertyName, aCallbacks)) + { + throwUnknown( aPropertyName ); + } + + if (aCallbacks.getter) + return aCallbacks.getter(); + + // TODO(Q1): subtlety, empty getter method silently returns + // the empty any + return uno::Any(); + } + + void PropertySetHelper::addPropertyChangeListener( const OUString& aPropertyName, + const uno::Reference< beans::XPropertyChangeListener >& /*xListener*/ ) + { + // check validity of property, but otherwise ignore the + // request + if( !isPropertyName( aPropertyName ) ) + throwUnknown( aPropertyName ); + } + + void PropertySetHelper::addVetoableChangeListener( const OUString& aPropertyName, + const uno::Reference< beans::XVetoableChangeListener >& /*xListener*/ ) + { + // check validity of property, but otherwise ignore the + // request + if( !isPropertyName( aPropertyName ) ) + throwUnknown( aPropertyName ); + } + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/tools/spriteredrawmanager.cxx b/canvas/source/tools/spriteredrawmanager.cxx new file mode 100644 index 000000000..c1595f459 --- /dev/null +++ b/canvas/source/tools/spriteredrawmanager.cxx @@ -0,0 +1,489 @@ +/* -*- 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 + +#include +#include + +namespace canvas +{ + namespace + { + /** Helper class to condense sprite updates into a single action + + This class tracks the sprite changes over the recorded + change list, and generates a single update action from + that (note that per screen update, several moves, + visibility changes and content updates might happen) + */ + class SpriteTracer + { + public: + explicit SpriteTracer( const Sprite::Reference& rAffectedSprite ) : + mpAffectedSprite(rAffectedSprite), + maMoveStartArea(), + maMoveEndArea(), + mbIsMove( false ), + mbIsGenericUpdate( false ) + { + } + + void operator()( const SpriteRedrawManager::SpriteChangeRecord& rSpriteRecord ) + { + // only deal with change events from the currently + // affected sprite + if( rSpriteRecord.mpAffectedSprite != mpAffectedSprite ) + return; + + switch( rSpriteRecord.meChangeType ) + { + case SpriteRedrawManager::SpriteChangeRecord::ChangeType::move: + if( !mbIsMove ) + { + // no move yet - this must be the first one + maMoveStartArea = ::basegfx::B2DRectangle( + rSpriteRecord.maOldPos, + rSpriteRecord.maOldPos + rSpriteRecord.maUpdateArea.getRange() ); + mbIsMove = true; + } + + maMoveEndArea = rSpriteRecord.maUpdateArea; + break; + + case SpriteRedrawManager::SpriteChangeRecord::ChangeType::update: + // update end update area of the + // sprite. Thus, every update() action + // _after_ the last move will correctly + // update the final repaint area. And this + // does not interfere with subsequent + // moves, because moves always perform a + // hard set of maMoveEndArea to their + // stored value + maMoveEndArea.expand( rSpriteRecord.maUpdateArea ); + mbIsGenericUpdate = true; + break; + + default: + ENSURE_OR_THROW( false, + "Unexpected case in SpriteUpdater::operator()" ); + break; + } + } + + void commit( SpriteRedrawManager::SpriteConnectedRanges& rUpdateCollector ) const + { + if( mbIsMove ) + { + if( !maMoveStartArea.isEmpty() || + !maMoveEndArea.isEmpty() ) + { + // if mbIsGenericUpdate is false, this is a + // pure move (i.e. no other update + // operations). Pass that information on to + // the SpriteInfo + const bool bIsPureMove( !mbIsGenericUpdate ); + + // ignore the case that start and end update + // area overlap - the b2dconnectedranges + // handle that, anyway. doing it this way + // ensures that we have both old and new area + // stored + + // round all given range up to enclosing + // integer rectangle - since the whole thing + // here is about + + // first, draw the new sprite position + rUpdateCollector.addRange( + ::basegfx::unotools::b2DSurroundingIntegerRangeFromB2DRange( maMoveEndArea ), + SpriteRedrawManager::SpriteInfo( + mpAffectedSprite, + maMoveEndArea, + true, + bIsPureMove ) ); + + // then, clear the old place (looks smoother + // this way) + rUpdateCollector.addRange( + ::basegfx::unotools::b2DSurroundingIntegerRangeFromB2DRange( maMoveStartArea ), + SpriteRedrawManager::SpriteInfo( + Sprite::Reference(), + maMoveStartArea, + true, + bIsPureMove ) ); + } + } + else if( mbIsGenericUpdate && + !maMoveEndArea.isEmpty() ) + { + rUpdateCollector.addRange( + ::basegfx::unotools::b2DSurroundingIntegerRangeFromB2DRange( maMoveEndArea ), + SpriteRedrawManager::SpriteInfo( + mpAffectedSprite, + maMoveEndArea, + true ) ); + } + } + + private: + Sprite::Reference mpAffectedSprite; + ::basegfx::B2DRectangle maMoveStartArea; + ::basegfx::B2DRectangle maMoveEndArea; + + /// True, if at least one move was encountered + bool mbIsMove; + + /// True, if at least one generic update was encountered + bool mbIsGenericUpdate; + }; + + + /** SpriteChecker functor, which for every sprite checks the + given update vector for necessary screen updates + */ + class SpriteUpdater + { + public: + /** Generate update area list + + @param rUpdater + Reference to an updater object, which will receive the + update areas. + + @param rChangeContainer + Container with all sprite change requests + + */ + SpriteUpdater( SpriteRedrawManager::SpriteConnectedRanges& rUpdater, + const SpriteRedrawManager::VectorOfChangeRecords& rChangeContainer ) : + mrUpdater( rUpdater ), + mrChangeContainer( rChangeContainer ) + { + } + + /** Call this method for every sprite on your screen + + This method scans the change container, collecting all + update info for the given sprite into one or two + update operations, which in turn are inserted into the + connected ranges processor. + + @param rSprite + Current sprite to collect update info for. + */ + void operator()( const Sprite::Reference& rSprite ) + { + SpriteTracer aSpriteTracer( rSprite ); + + for (auto const& aChange : mrChangeContainer) + aSpriteTracer( aChange ); + + aSpriteTracer.commit( mrUpdater ); + } + + private: + SpriteRedrawManager::SpriteConnectedRanges& mrUpdater; + const SpriteRedrawManager::VectorOfChangeRecords& mrChangeContainer; + }; + } + + void SpriteRedrawManager::setupUpdateAreas( SpriteConnectedRanges& rUpdateAreas ) const + { + // TODO(T3): This is NOT thread safe at all. This only works + // under the assumption that NOBODY changes ANYTHING + // concurrently, while this method is on the stack. We should + // really rework the canvas::Sprite interface, in such a way + // that it dumps ALL its state with a single, atomic + // call. Then, we store that state locally. This prolly goes + // in line with the problem of having sprite state available + // for the frame before the last frame; plus, it avoids + // frequent locks of the object mutexes + SpriteWeakOrder aSpriteComparator; + + // put all sprites that have changed content into update areas + for( const auto& pSprite : maSprites ) + { + if( pSprite->isContentChanged() ) + const_cast< SpriteRedrawManager* >( this )->updateSprite( pSprite, + pSprite->getPosPixel(), + pSprite->getUpdateArea() ); + } + + // sort sprites after prio + VectorOfSprites aSortedSpriteVector; + std::copy( maSprites.begin(), + maSprites.end(), + std::back_insert_iterator< VectorOfSprites >(aSortedSpriteVector) ); + std::sort( aSortedSpriteVector.begin(), + aSortedSpriteVector.end(), + aSpriteComparator ); + + // extract all referenced sprites from the maChangeRecords + // (copy sprites, make the list unique, regarding the + // sprite pointer). This assumes that, until this scope + // ends, nobody changes the maChangeRecords vector! + VectorOfSprites aUpdatableSprites; + for( const auto& rChangeRecord : maChangeRecords ) + { + const Sprite::Reference& rSprite( rChangeRecord.getSprite() ); + if( rSprite.is() ) + aUpdatableSprites.push_back( rSprite ); + } + + std::sort( aUpdatableSprites.begin(), + aUpdatableSprites.end(), + aSpriteComparator ); + + VectorOfSprites::iterator aEnd= + std::unique( aUpdatableSprites.begin(), + aUpdatableSprites.end() ); + + // for each unique sprite, check the change event vector, + // calculate the update operation from that, and add the + // result to the aUpdateArea. + std::for_each( aUpdatableSprites.begin(), + aEnd, + SpriteUpdater( rUpdateAreas, + maChangeRecords) ); + + // TODO(P2): Implement your own output iterator adapter, to + // avoid that totally superfluous temp aUnchangedSprites + // vector. + + // add all sprites to rUpdateAreas, that are _not_ already + // contained in the uniquified vector of changed ones + // (i.e. the difference between aSortedSpriteVector and + // aUpdatableSprites). + VectorOfSprites aUnchangedSprites; + std::set_difference( aSortedSpriteVector.begin(), + aSortedSpriteVector.end(), + aUpdatableSprites.begin(), + aEnd, + std::back_insert_iterator< VectorOfSprites >(aUnchangedSprites), + aSpriteComparator ); + + // add each remaining unchanged sprite to connected ranges, + // marked as "don't need update" + for( const auto& pUnchangedSprite : aUnchangedSprites ) + { + const ::basegfx::B2DRange& rUpdateArea( pUnchangedSprite->getUpdateArea() ); + rUpdateAreas.addRange( + ::basegfx::unotools::b2DSurroundingIntegerRangeFromB2DRange( rUpdateArea ), + SpriteInfo( pUnchangedSprite, + rUpdateArea, + false ) ); + } + } + +#if OSL_DEBUG_LEVEL > 0 + static bool impIsEqualB2DRange(const basegfx::B2DRange& rRangeA, const basegfx::B2DRange& rRangeB, double fSmallValue) + { + return fabs(rRangeB.getMinX() - rRangeA.getMinX()) <= fSmallValue + && fabs(rRangeB.getMinY() - rRangeA.getMinY()) <= fSmallValue + && fabs(rRangeB.getMaxX() - rRangeA.getMaxX()) <= fSmallValue + && fabs(rRangeB.getMaxY() - rRangeA.getMaxY()) <= fSmallValue; + } + + static bool impIsEqualB2DVector(const basegfx::B2DVector& rVecA, const basegfx::B2DVector& rVecB, double fSmallValue) + { + return fabs(rVecB.getX() - rVecA.getX()) <= fSmallValue + && fabs(rVecB.getY() - rVecA.getY()) <= fSmallValue; + } +#endif + + bool SpriteRedrawManager::isAreaUpdateScroll( ::basegfx::B2DRectangle& o_rMoveStart, + ::basegfx::B2DRectangle& o_rMoveEnd, + const UpdateArea& rUpdateArea, + std::size_t nNumSprites ) const + { + // check for a solitary move, which consists of exactly two + // pure-move entries, the first with valid, the second with + // invalid sprite (see SpriteTracer::commit()). Note that we + // cannot simply store some flag in SpriteTracer::commit() + // above and just check that here, since during the connected + // range calculations, other sprites might get merged into the + // same region (thus spoiling the scrolling move + // optimization). + if( nNumSprites != 2 ) + return false; + + const SpriteConnectedRanges::ComponentListType::const_iterator aFirst( + rUpdateArea.maComponentList.begin() ); + SpriteConnectedRanges::ComponentListType::const_iterator aSecond( + aFirst ); + ++aSecond; + + if( !aFirst->second.isPureMove() || + !aSecond->second.isPureMove() || + !aFirst->second.getSprite().is() || + // use _true_ update area, not the rounded version + !aFirst->second.getSprite()->isAreaUpdateOpaque( aFirst->second.getUpdateArea() ) || + aSecond->second.getSprite().is() ) + { + // either no move update, or incorrect sprite, or sprite + // content not fully opaque over update region. + return false; + } + + o_rMoveStart = aSecond->second.getUpdateArea(); + o_rMoveEnd = aFirst->second.getUpdateArea(); + +#if OSL_DEBUG_LEVEL > 0 + ::basegfx::B2DRectangle aTotalBounds( o_rMoveStart ); + aTotalBounds.expand( o_rMoveEnd ); + + SAL_WARN_IF(!impIsEqualB2DRange(rUpdateArea.maTotalBounds, basegfx::unotools::b2DSurroundingIntegerRangeFromB2DRange(aTotalBounds), 0.5), + "canvas", + "SpriteRedrawManager::isAreaUpdateScroll(): sprite area and total area mismatch"); + SAL_WARN_IF(!impIsEqualB2DVector(o_rMoveStart.getRange(), o_rMoveEnd.getRange(), 0.5), + "canvas", + "SpriteRedrawManager::isAreaUpdateScroll(): scroll start and end area have mismatching size"); +#endif + + return true; + } + + bool SpriteRedrawManager::isAreaUpdateNotOpaque( const ::basegfx::B2DRectangle& rUpdateRect, + const AreaComponent& rComponent ) const + { + const Sprite::Reference& pAffectedSprite( rComponent.second.getSprite() ); + + if( !pAffectedSprite.is() ) + return true; // no sprite, no opaque update! + + return !pAffectedSprite->isAreaUpdateOpaque( rUpdateRect ); + } + + bool SpriteRedrawManager::isAreaUpdateOpaque( const UpdateArea& rUpdateArea, + std::size_t nNumSprites ) const + { + // check whether the sprites in the update area's list will + // fully cover the given area _and_ do that in an opaque way + // (i.e. no alpha, no non-rectangular sprite content). + + // TODO(P1): Come up with a smarter early-exit criterion here + // (though, I think, the case that _lots_ of sprites _fully_ + // cover a rectangular area _without_ any holes is extremely + // improbable) + + // avoid checking large number of sprites (and probably fail, + // anyway). Note: the case nNumSprites < 1 should normally not + // happen, as handleArea() calls backgroundPaint() then. + if( nNumSprites > 3 || nNumSprites < 1 ) + return false; + + // now, calc the _true_ update area, by merging all sprite's + // true update areas into one rectangle + ::basegfx::B2DRange aTrueArea( rUpdateArea.maComponentList.begin()->second.getUpdateArea() ); + for( const auto& rArea : rUpdateArea.maComponentList ) + aTrueArea.expand(rArea.second.getUpdateArea()); + + const SpriteConnectedRanges::ComponentListType::const_iterator aEnd( + rUpdateArea.maComponentList.end() ); + + // and check whether _any_ of the sprites tells that its area + // update will not be opaque. + return std::none_of( rUpdateArea.maComponentList.begin(), + aEnd, + [&aTrueArea, this]( const std::pair< ::basegfx::B2DRange, SpriteInfo >& cp ) + { return this->isAreaUpdateNotOpaque(aTrueArea, cp); } ); + } + + bool SpriteRedrawManager::areSpritesChanged( const UpdateArea& rUpdateArea ) const + { + // check whether SpriteInfo::needsUpdate returns false for + // all elements of this area's contained sprites + + // if not a single changed sprite found - just ignore this + // component (return false) + const SpriteConnectedRanges::ComponentListType::const_iterator aEnd( + rUpdateArea.maComponentList.end() ); + return std::any_of( rUpdateArea.maComponentList.begin(), + aEnd, + []( const std::pair< ::basegfx::B2DRange, SpriteInfo >& cp ) + { return cp.second.needsUpdate(); } ); + } + + SpriteRedrawManager::SpriteRedrawManager() : + maSprites(), + maChangeRecords() + { + } + + void SpriteRedrawManager::disposing() + { + // drop all references + maChangeRecords.clear(); + + // dispose all sprites - the spritecanvas, and by delegation, + // this object, is the owner of the sprites. After all, a + // sprite without a canvas to render into makes not terribly + // much sense. + for( const auto& rCurr : boost::adaptors::reverse(maSprites) ) + rCurr->dispose(); + + maSprites.clear(); + } + + void SpriteRedrawManager::clearChangeRecords() + { + maChangeRecords.clear(); + } + + void SpriteRedrawManager::showSprite( const Sprite::Reference& rSprite ) + { + maSprites.push_back( rSprite ); + } + + void SpriteRedrawManager::hideSprite( const Sprite::Reference& rSprite ) + { + maSprites.erase(std::remove(maSprites.begin(), maSprites.end(), rSprite), maSprites.end()); + } + + void SpriteRedrawManager::moveSprite( const Sprite::Reference& rSprite, + const ::basegfx::B2DPoint& rOldPos, + const ::basegfx::B2DPoint& rNewPos, + const ::basegfx::B2DVector& rSpriteSize ) + { + maChangeRecords.emplace_back( rSprite, + rOldPos, + rNewPos, + rSpriteSize ); + } + + void SpriteRedrawManager::updateSprite( const Sprite::Reference& rSprite, + const ::basegfx::B2DPoint& rPos, + const ::basegfx::B2DRange& rUpdateArea ) + { + maChangeRecords.emplace_back( rSprite, + rPos, + rUpdateArea ); + } + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/tools/surface.cxx b/canvas/source/tools/surface.cxx new file mode 100644 index 000000000..10c2d4655 --- /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 + +#include +#include +#include + +#include "surface.hxx" + +namespace canvas +{ + Surface::Surface( const PageManagerSharedPtr& rPageManager, + const std::shared_ptr& rColorBuffer, + const ::basegfx::B2IPoint& rPos, + const ::basegfx::B2ISize& rSize ) : + mpColorBuffer(rColorBuffer), + mpPageManager(rPageManager), + mpFragment(), + 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 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(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.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(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.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(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: */ diff --git a/canvas/source/tools/surface.hxx b/canvas/source/tools/surface.hxx new file mode 100644 index 000000000..e791e6b50 --- /dev/null +++ b/canvas/source/tools/surface.hxx @@ -0,0 +1,146 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_CANVAS_SOURCE_TOOLS_SURFACE_HXX +#define INCLUDED_CANVAS_SOURCE_TOOLS_SURFACE_HXX + +#include +#include +#include +#include +#include +#include +#include + +#include "pagemanager.hxx" + +namespace canvas +{ + /** surfaces denote occupied areas within pages. + + pages encapsulate the hardware buffers that + contain image data which can be used for texturing. + surfaces are areas within those pages. + */ + class Surface + { + public: + + Surface( const PageManagerSharedPtr& rPageManager, + const std::shared_ptr& rColorBuffer, + const ::basegfx::B2IPoint& rPos, + const ::basegfx::B2ISize& rSize ); + ~Surface(); + + void setColorBufferDirty(); + + /** Render the surface content to screen. + + @param fAlpha + Overall alpha for content + + @param rPos + Output position + + @param rTransform + Output transformation (does not affect output position) + */ + bool draw( double fAlpha, + const ::basegfx::B2DPoint& rPos, + const ::basegfx::B2DHomMatrix& rTransform ); + + /** Render the surface content to screen. + + @param fAlpha + Overall alpha for content + + @param rPos + Output position + + @param rArea + Subset of the surface to render. Coordinate system are + surface area pixel, given area will be clipped to the + surface bounds. + + @param rTransform + Output transformation (does not affect output position) + */ + bool drawRectangularArea( + double fAlpha, + const ::basegfx::B2DPoint& rPos, + const ::basegfx::B2DRectangle& rArea, + const ::basegfx::B2DHomMatrix& rTransform ); + + /** Render the surface content to screen. + + @param fAlpha + Overall alpha for content + + @param rPos + Output position + + @param rClipPoly + Clip polygon for the surface. The clip polygon is also + subject to the output transformation. + + @param rTransform + Output transformation (does not affect output position) + */ + bool drawWithClip( double fAlpha, + const ::basegfx::B2DPoint& rPos, + const ::basegfx::B2DPolygon& rClipPoly, + const ::basegfx::B2DHomMatrix& rTransform ); + + private: + std::shared_ptr mpColorBuffer; + + // invoking any of the above defined 'draw' methods + // will forward primitive commands to the rendermodule. + PageManagerSharedPtr mpPageManager; + + FragmentSharedPtr mpFragment; + + // the offset of this surface with regard to the source + // image. if the source image had to be tiled into multiple + // surfaces, this offset denotes the relative pixel distance + // from the source image's upper, left corner + ::basegfx::B2IPoint maSourceOffset; + + // the size in pixels of this surface. please note that + // this size is likely to be smaller than the size of + // the colorbuffer we're associated with since we + // maybe represent only a part of it. + ::basegfx::B2ISize maSize; + + bool mbIsDirty; + + private: + void prepareRendering(); + + basegfx::B2DRectangle getUVCoords() const; + basegfx::B2DRectangle getUVCoords( const ::basegfx::B2IPoint& rPos, + const ::basegfx::B2ISize& rSize ) const; + }; + + typedef std::shared_ptr< Surface > SurfaceSharedPtr; +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/tools/surfaceproxy.cxx b/canvas/source/tools/surfaceproxy.cxx new file mode 100644 index 000000000..b5c2b52f9 --- /dev/null +++ b/canvas/source/tools/surfaceproxy.cxx @@ -0,0 +1,141 @@ +/* -*- 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 "surfaceproxy.hxx" + +namespace canvas +{ + SurfaceProxy::SurfaceProxy( const std::shared_ptr& pBuffer, + const PageManagerSharedPtr& pPageManager ) : + mpPageManager( pPageManager ), + maSurfaceList(), + mpBuffer( pBuffer ) + { + const ::basegfx::B2ISize aImageSize(mpBuffer->getWidth(),mpBuffer->getHeight()); + const ::basegfx::B2ISize aPageSize(mpPageManager->getPageSize()); + const sal_Int32 aPageSizeX(aPageSize.getX()); + const sal_Int32 aPageSizeY(aPageSize.getY()); + const sal_Int32 aImageSizeX(aImageSize.getX()); + const sal_Int32 aImageSizeY(aImageSize.getY()); + + // see if the size of the colorbuffer is larger than the size + // of a single page. if this is the case we divide the + // colorbuffer into as many surfaces as we need to get the + // whole area distributed. otherwise (the colorbuffer is + // smaller than the size of a single page) we search for free + // pages or create a new one. + // the incoming image is too large to fit into a single + // page. strategy: we split the image into rectangular + // areas that are as large as the maximum page size + // dictates and follow the strategy for fitting images. + size_t dwNumSurfaces(0); + for(sal_Int32 y=0; y( + mpPageManager, + mpBuffer, + aOffset, + aSize)); + } + } + } + + void SurfaceProxy::setColorBufferDirty() + { + for( const auto& rSurfacePtr : maSurfaceList ) + rSurfacePtr->setColorBufferDirty(); + } + + bool SurfaceProxy::draw( double fAlpha, + const ::basegfx::B2DPoint& rPos, + const ::basegfx::B2DHomMatrix& rTransform ) + { + for( const auto& rSurfacePtr : maSurfaceList ) + rSurfacePtr->draw( fAlpha, rPos, rTransform ); + + return true; + } + + bool SurfaceProxy::draw( double fAlpha, + const ::basegfx::B2DPoint& rPos, + const ::basegfx::B2DRange& rArea, + const ::basegfx::B2DHomMatrix& rTransform ) + { + for( const auto& rSurfacePtr : maSurfaceList ) + rSurfacePtr->drawRectangularArea( fAlpha, rPos, rArea, rTransform ); + + return true; + } + + bool SurfaceProxy::draw( double fAlpha, + const ::basegfx::B2DPoint& rPos, + const ::basegfx::B2DPolyPolygon& rClipPoly, + const ::basegfx::B2DHomMatrix& rTransform ) + { + const ::basegfx::triangulator::B2DTriangleVector& rTriangulatedVector( + ::basegfx::triangulator::triangulate(rClipPoly)); + + // we have now an explicit ::B2DTriangle and ::B2DTriangleVector, + // but I do not know enough about 'drawWithClip' or 'clipTriangleListOnRange' + // to adapt to that. Convert back to old three-point-in-polygon convention + ::basegfx::B2DPolygon aTriangulatedPolygon; + aTriangulatedPolygon.reserve(rTriangulatedVector.size() * 3); + + for(const auto& rCandidate : rTriangulatedVector) + { + aTriangulatedPolygon.append(rCandidate.getA()); + aTriangulatedPolygon.append(rCandidate.getB()); + aTriangulatedPolygon.append(rCandidate.getC()); + } + + // dump polygons + SAL_INFO("canvas", "Original clip polygon: " << basegfx::utils::exportToSvgD( rClipPoly, true, true, false )); + SAL_INFO("canvas", "Triangulated polygon: " << basegfx::utils::exportToSvgD(basegfx::B2DPolyPolygon(aTriangulatedPolygon), true, true, false )); + + for( const auto& rSurfacePtr : maSurfaceList ) + rSurfacePtr->drawWithClip( fAlpha, rPos, aTriangulatedPolygon, rTransform ); + + return true; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/tools/surfaceproxy.hxx b/canvas/source/tools/surfaceproxy.hxx new file mode 100644 index 000000000..fb9308a61 --- /dev/null +++ b/canvas/source/tools/surfaceproxy.hxx @@ -0,0 +1,121 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_CANVAS_SOURCE_TOOLS_SURFACEPROXY_HXX +#define INCLUDED_CANVAS_SOURCE_TOOLS_SURFACEPROXY_HXX + +#include +#include + +#include "pagemanager.hxx" +#include "surface.hxx" + +namespace canvas +{ + /** Definition of the surface proxy class. + + Surface proxies are the connection between *one* source image + and *one or more* hardware surfaces (or textures). in a + logical structure surface proxies represent solely this + dependency plus some simple cache management. + */ + class SurfaceProxy : public ISurfaceProxy + { + public: + + SurfaceProxy( const std::shared_ptr& pBuffer, + const PageManagerSharedPtr &pPageManager ); + + // ISurfaceProxy interface + virtual void setColorBufferDirty() override; + + /** Render the surface content to screen. + + @param fAlpha + Overall alpha for content + + @param rPos + Output position + + @param rTransform + Output transformation (does not affect output position) + */ + virtual bool draw( double fAlpha, + const ::basegfx::B2DPoint& rPos, + const ::basegfx::B2DHomMatrix& rTransform ) override; + + /** Render the surface content to screen. + + @param fAlpha + Overall alpha for content + + @param rPos + Output position + + @param rArea + Subset of the surface to render. Coordinate system are + surface area pixel, given area will be clipped to the + surface bounds. + + @param rTransform + Output transformation (does not affect output position) + */ + virtual bool draw( double fAlpha, + const ::basegfx::B2DPoint& rPos, + const ::basegfx::B2DRange& rArea, + const ::basegfx::B2DHomMatrix& rTransform ) override; + + /** Render the surface content to screen. + + @param fAlpha + Overall alpha for content + + @param rPos + Output position + + @param rClipPoly + Clip polygon for the surface. The clip polygon is also + subject to the output transformation. + + @param rTransform + Output transformation (does not affect output position) + */ + virtual bool draw( double fAlpha, + const ::basegfx::B2DPoint& rPos, + const ::basegfx::B2DPolyPolygon& rClipPoly, + const ::basegfx::B2DHomMatrix& rTransform ) override; + + private: + PageManagerSharedPtr mpPageManager; + + // the pagemanager will distribute the image + // to one or more surfaces, this is why we + // need a list here. + std::vector maSurfaceList; + + // pointer to the source of image data + // which always is stored in system memory, + // 32bit rgba and can have any size. + std::shared_ptr mpBuffer; + }; +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/tools/surfaceproxymanager.cxx b/canvas/source/tools/surfaceproxymanager.cxx new file mode 100644 index 000000000..b60c5ecb4 --- /dev/null +++ b/canvas/source/tools/surfaceproxymanager.cxx @@ -0,0 +1,73 @@ +/* -*- 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 "surfaceproxy.hxx" + +namespace canvas +{ + namespace { + + class SurfaceProxyManager : public ISurfaceProxyManager + { + public: + + explicit SurfaceProxyManager( const std::shared_ptr& rRenderModule ) : + mpPageManager( std::make_shared(rRenderModule) ) + { + } + + /** the whole idea is build around the concept that you create + some arbitrary buffer which contains the image data and + tell the texture manager about it. from there on you can + draw this image using any kind of graphics api you want. + in the technical sense we allocate some space in local + videomemory or AGP memory which will be filled on demand, + which means if there exists any rendering operation that + needs to read from this memory location. this method + creates a logical hardware surface object which uses the + given color buffer as the image source. internally this + texture may be distributed to several real hardware + surfaces. + */ + virtual std::shared_ptr createSurfaceProxy( const std::shared_ptr& pBuffer ) const override + { + // not much to do for now, simply allocate a new surface + // proxy from our internal pool and initialize this thing + // properly. we *don't* create a hardware surface for now. + return std::make_shared(pBuffer,mpPageManager); + } + + private: + PageManagerSharedPtr mpPageManager; + }; + + } + + std::shared_ptr createSurfaceProxyManager( const std::shared_ptr& rRenderModule ) + { + return std::make_shared(rRenderModule); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/tools/surfacerect.hxx b/canvas/source/tools/surfacerect.hxx new file mode 100644 index 000000000..ec73342ea --- /dev/null +++ b/canvas/source/tools/surfacerect.hxx @@ -0,0 +1,93 @@ +/* -*- 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 . + */ + +#ifndef INCLUDED_CANVAS_SOURCE_TOOLS_SURFACERECT_HXX +#define INCLUDED_CANVAS_SOURCE_TOOLS_SURFACERECT_HXX + +#include +#include + +namespace canvas +{ + /** + * This implements some equivalent to basegfx::B2IBox, but instead of two + * BasicBox ranges, it uses a position and a size. maPos and maSize could + * be replaced by: + * - B2IPoint(getMinX(), getMinY()) and + * - B2ISize(getMaxX()-getMinX(), getMaxY()-getMinY()) + * + * The current allocation algorithm uses size and pos a lot. Not sure how + * time-critical any of this code is and if that would be a problem. + */ + struct SurfaceRect + { + ::basegfx::B2IPoint maPos; + ::basegfx::B2ISize maSize; + + explicit SurfaceRect( const ::basegfx::B2ISize &rSize ) : + maPos(), + maSize(rSize) + { + } + + bool pointInside( sal_Int32 px, sal_Int32 py ) const + { + const sal_Int32 x1(maPos.getX()); + const sal_Int32 y1(maPos.getY()); + const sal_Int32 x2(x1 + maSize.getX()); + const sal_Int32 y2(y1 + maSize.getY()); + if(px < x1) return false; + if(px >= x2) return false; + if(py < y1) return false; + if(py >= y2) return false; + return true; + } + + /// returns true if the passed rect intersects this one. + bool intersection( const SurfaceRect& r ) const + { + const sal_Int32 x1(maPos.getX()); + const sal_Int32 y1(maPos.getY()); + const sal_Int32 x1w(x1 + maSize.getX() - 1); + const sal_Int32 y1h(y1 + maSize.getY() - 1); + + const sal_Int32 x2(r.maPos.getX()); + const sal_Int32 y2(r.maPos.getY()); + const sal_Int32 x2w(x2 + r.maSize.getX() - 1); + const sal_Int32 y2h(y2 + r.maSize.getY() - 1); + + return !((x1w < x2) || (x2w < x1) || (y1h < y2) || (y2h < y1)); + } + + bool inside( const SurfaceRect& r ) const + { + const sal_Int32 x1(maPos.getX()); + const sal_Int32 y1(maPos.getY()); + const sal_Int32 x2(x1 + maSize.getX() - 1); + const sal_Int32 y2(y1 + maSize.getY() - 1); + if(!(r.pointInside(x1,y1))) return false; + if(!(r.pointInside(x2,y2))) return false; + return true; + } + }; +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/tools/verifyinput.cxx b/canvas/source/tools/verifyinput.cxx new file mode 100644 index 000000000..de69da5e7 --- /dev/null +++ b/canvas/source/tools/verifyinput.cxx @@ -0,0 +1,713 @@ +/* -*- 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + + +using namespace ::com::sun::star; + +namespace canvas::tools +{ + void verifyInput( const geometry::RealPoint2D& rPoint, + const char* pStr, + const uno::Reference< uno::XInterface >& xIf, + ::sal_Int16 nArgPos ) + { +#if OSL_DEBUG_LEVEL > 0 + if( !std::isfinite( rPoint.X ) ) + { + throw lang::IllegalArgumentException( + OUString::createFromAscii( pStr ) + ": verifyInput(): point X value contains infinite or NAN", + xIf, nArgPos ); + } + + if( !std::isfinite( rPoint.Y ) ) + { + throw lang::IllegalArgumentException( + OUString::createFromAscii( pStr ) + ": verifyInput(): point X value contains infinite or NAN", + xIf, nArgPos ); + } +#else + (void)pStr; (void)xIf; (void)nArgPos; + if( !std::isfinite( rPoint.X ) || + !std::isfinite( rPoint.Y ) ) + { + throw lang::IllegalArgumentException(); + } +#endif + } + + void verifyInput( const geometry::RealBezierSegment2D& rSegment, + const char* pStr, + const uno::Reference< uno::XInterface >& xIf, + ::sal_Int16 nArgPos ) + { +#if OSL_DEBUG_LEVEL > 0 + if( !std::isfinite( rSegment.Px ) ) + { + throw lang::IllegalArgumentException( + OUString::createFromAscii( pStr ) + + ": verifyInput(): bezier segment's Px value contains infinite or NAN", + xIf, nArgPos ); + } + + if( !std::isfinite( rSegment.Py ) ) + { + throw lang::IllegalArgumentException( + OUString::createFromAscii( pStr ) + + ": verifyInput(): bezier segment's Py value contains infinite or NAN", + xIf, nArgPos ); + } + + if( !std::isfinite( rSegment.C1x ) ) + { + throw lang::IllegalArgumentException( + OUString::createFromAscii( pStr ) + + ": verifyInput(): bezier segment's C1x value contains infinite or NAN", + xIf, nArgPos ); + } + + if( !std::isfinite( rSegment.C1y ) ) + { + throw lang::IllegalArgumentException( + OUString::createFromAscii( pStr ) + + ": verifyInput(): bezier segment's C1y value contains infinite or NAN", + xIf, nArgPos ); + } + + if( !std::isfinite( rSegment.C2x ) ) + { + throw lang::IllegalArgumentException( + OUString::createFromAscii( pStr ) + + ": verifyInput(): bezier segment's C2x value contains infinite or NAN", + xIf, nArgPos ); + } + + if( !std::isfinite( rSegment.C2y ) ) + { + throw lang::IllegalArgumentException( + OUString::createFromAscii( pStr ) + + ": verifyInput(): bezier segment's C2y value contains infinite or NAN", + xIf, nArgPos ); + } +#else + (void)pStr; (void)xIf; (void)nArgPos; + if( !std::isfinite( rSegment.Px ) || + !std::isfinite( rSegment.Py ) || + !std::isfinite( rSegment.C1x ) || + !std::isfinite( rSegment.C1y ) || + !std::isfinite( rSegment.C2x ) || + !std::isfinite( rSegment.C2y ) ) + { + throw lang::IllegalArgumentException(); + } +#endif + } + + void verifyInput( const geometry::RealRectangle2D& rRect, + const char* pStr, + const uno::Reference< uno::XInterface >& xIf, + ::sal_Int16 nArgPos ) + { +#if OSL_DEBUG_LEVEL > 0 + if( !std::isfinite( rRect.X1 ) ) + { + throw lang::IllegalArgumentException( + OUString::createFromAscii(pStr) + + ": verifyInput(): rectangle point X1 contains infinite or NAN", + xIf, nArgPos ); + } + + if( !std::isfinite( rRect.Y1 ) ) + { + throw lang::IllegalArgumentException( + OUString::createFromAscii(pStr) + + ": verifyInput(): rectangle point Y1 contains infinite or NAN", + xIf, nArgPos ); + } + + if( !std::isfinite( rRect.X2 ) ) + { + throw lang::IllegalArgumentException( + OUString::createFromAscii(pStr) + + ": verifyInput(): rectangle point X2 contains infinite or NAN", + xIf, nArgPos ); + } + + if( !std::isfinite( rRect.Y2 ) ) + { + throw lang::IllegalArgumentException( + OUString::createFromAscii(pStr) + + ": verifyInput(): rectangle point Y2 contains infinite or NAN", + xIf, nArgPos ); + } +#else + (void)pStr; (void)xIf; (void)nArgPos; + if( !std::isfinite( rRect.X1 ) || + !std::isfinite( rRect.Y1 ) || + !std::isfinite( rRect.X2 ) || + !std::isfinite( rRect.Y2 ) ) + { + throw lang::IllegalArgumentException(); + } +#endif + } + + void verifyInput( const geometry::AffineMatrix2D& matrix, + const char* pStr, + const uno::Reference< uno::XInterface >& xIf, + ::sal_Int16 nArgPos ) + { +#if OSL_DEBUG_LEVEL > 0 + const sal_Int32 nBinaryState( + 100000 * int(!std::isfinite( matrix.m00 )) + + 10000 * int(!std::isfinite( matrix.m01 )) + + 1000 * int(!std::isfinite( matrix.m02 )) + + 100 * int(!std::isfinite( matrix.m10 )) + + 10 * int(!std::isfinite( matrix.m11 )) + + 1 * int(!std::isfinite( matrix.m12 )) ); + + if( nBinaryState ) + { + throw lang::IllegalArgumentException( + OUString::createFromAscii(pStr) + + ": verifyInput(): AffineMatrix2D contains infinite or NAN value(s) at the following positions (m00-m12): " + + OUString::number(nBinaryState), + xIf, nArgPos ); + } +#else + (void)pStr; (void)xIf; (void)nArgPos; + if( !std::isfinite( matrix.m00 ) || + !std::isfinite( matrix.m01 ) || + !std::isfinite( matrix.m02 ) || + !std::isfinite( matrix.m10 ) || + !std::isfinite( matrix.m11 ) || + !std::isfinite( matrix.m12 ) ) + { + throw lang::IllegalArgumentException(); + } +#endif + } + + void verifyInput( const geometry::Matrix2D& matrix, + const char* pStr, + const uno::Reference< uno::XInterface >& xIf, + ::sal_Int16 nArgPos ) + { +#if OSL_DEBUG_LEVEL > 0 + const sal_Int32 nBinaryState( + 1000 * int(!std::isfinite( matrix.m00 )) + + 100 * int(!std::isfinite( matrix.m01 )) + + 10 * int(!std::isfinite( matrix.m10 )) + + 1 * int(!std::isfinite( matrix.m11 )) ); + + if( nBinaryState ) + { + throw lang::IllegalArgumentException( + OUString::createFromAscii(pStr) + + ": verifyInput(): Matrix2D contains infinite or NAN value(s) at the following positions (m00-m11): " + + OUString::number(nBinaryState), + xIf, nArgPos ); + } +#else + (void)pStr; (void)xIf; (void)nArgPos; + if( !std::isfinite( matrix.m00 ) || + !std::isfinite( matrix.m01 ) || + !std::isfinite( matrix.m10 ) || + !std::isfinite( matrix.m11 ) ) + { + throw lang::IllegalArgumentException(); + } +#endif + } + + void verifyInput( const rendering::ViewState& viewState, + const char* pStr, + const uno::Reference< uno::XInterface >& xIf, + ::sal_Int16 nArgPos ) + { + verifyInput( viewState.AffineTransform, + pStr, xIf, nArgPos ); + } + + void verifyInput( const rendering::RenderState& renderState, + const char* pStr, + const uno::Reference< uno::XInterface >& xIf, + ::sal_Int16 nArgPos, + sal_Int32 nMinColorComponents ) + { + verifyInput( renderState.AffineTransform, + pStr, xIf, nArgPos ); + + if( renderState.DeviceColor.getLength() < nMinColorComponents ) + { +#if OSL_DEBUG_LEVEL > 0 + throw lang::IllegalArgumentException( + OUString::createFromAscii(pStr) + + ": verifyInput(): render state's device color has too few components (" + + OUString::number(nMinColorComponents) + + " expected, " + + OUString::number(renderState.DeviceColor.getLength()) + + " provided)", + xIf, nArgPos ); +#else + throw lang::IllegalArgumentException(); +#endif + } + + if( !(renderState.CompositeOperation < rendering::CompositeOperation::CLEAR || + renderState.CompositeOperation > rendering::CompositeOperation::SATURATE) ) + return; + +#if OSL_DEBUG_LEVEL > 0 + throw lang::IllegalArgumentException( + OUString::createFromAscii(pStr) + + ": verifyInput(): render state's CompositeOperation value out of range (" + + OUString::number(sal::static_int_cast(renderState.CompositeOperation)) + + " not known)", + xIf, nArgPos ); +#else + throw lang::IllegalArgumentException(); +#endif + } + + void verifyInput( const rendering::Texture& texture, + const char* pStr, + const uno::Reference< uno::XInterface >& xIf, + ::sal_Int16 nArgPos ) + { + verifyInput( texture.AffineTransform, + pStr, xIf, nArgPos ); + + if( !std::isfinite( texture.Alpha ) || + texture.Alpha < 0.0 || + texture.Alpha > 1.0 ) + { +#if OSL_DEBUG_LEVEL > 0 + throw lang::IllegalArgumentException( + OUString::createFromAscii(pStr) + + ": verifyInput(): textures' alpha value out of range (is " + + OUString::number(texture.Alpha) + ")", + xIf, nArgPos ); +#else + throw lang::IllegalArgumentException(); +#endif + } + + if( texture.NumberOfHatchPolygons < 0 ) + { +#if OSL_DEBUG_LEVEL > 0 + throw lang::IllegalArgumentException( + OUString::createFromAscii(pStr) + + ": verifyInput(): textures' NumberOfHatchPolygons is negative", + xIf, nArgPos ); +#else + throw lang::IllegalArgumentException(); +#endif + } + + if( texture.RepeatModeX < rendering::TexturingMode::NONE || + texture.RepeatModeX > rendering::TexturingMode::REPEAT ) + { +#if OSL_DEBUG_LEVEL > 0 + throw lang::IllegalArgumentException( + OUString::createFromAscii(pStr) + + ": verifyInput(): textures' RepeatModeX value is out of range (" + + OUString::number(sal::static_int_cast(texture.RepeatModeX)) + + " not known)", + xIf, nArgPos ); +#else + throw lang::IllegalArgumentException(); +#endif + } + + if( !(texture.RepeatModeY < rendering::TexturingMode::NONE || + texture.RepeatModeY > rendering::TexturingMode::REPEAT) ) + return; + +#if OSL_DEBUG_LEVEL > 0 + throw lang::IllegalArgumentException( + OUString::createFromAscii(pStr) + + ": verifyInput(): textures' RepeatModeY value is out of range (" + + OUString::number(sal::static_int_cast(texture.RepeatModeY)) + + " not known)", + xIf, nArgPos ); +#else + throw lang::IllegalArgumentException(); +#endif + } + + namespace + { + struct VerifyDashValue + { + VerifyDashValue( const char* pStr, + const uno::Reference< uno::XInterface >& xIf, + ::sal_Int16 nArgPos ) : + mpStr( pStr ), + mrIf( xIf ), + mnArgPos( nArgPos ) + { + } + + void operator()( const double& rVal ) + { + if( !std::isfinite( rVal ) || rVal < 0.0 ) + { +#if OSL_DEBUG_LEVEL > 0 + throw lang::IllegalArgumentException( + OUString::createFromAscii(mpStr) + + ": verifyInput(): one of stroke attributes' DashArray value out of range (is " + + OUString::number(rVal) + ")", + mrIf, mnArgPos ); +#else + throw lang::IllegalArgumentException(); +#endif + } + } + + const char* mpStr; + const uno::Reference< uno::XInterface >& mrIf; + sal_Int16 mnArgPos; + }; + } + + void verifyInput( const rendering::StrokeAttributes& strokeAttributes, + const char* pStr, + const uno::Reference< uno::XInterface >& xIf, + ::sal_Int16 nArgPos ) + { + if( !std::isfinite( strokeAttributes.StrokeWidth ) || + strokeAttributes.StrokeWidth < 0.0 ) + { +#if OSL_DEBUG_LEVEL > 0 + throw lang::IllegalArgumentException( + OUString::createFromAscii(pStr) + + ": verifyInput(): stroke attributes' StrokeWidth value out of range (is " + + OUString::number(strokeAttributes.StrokeWidth) + + ")", + xIf, nArgPos ); +#else + throw lang::IllegalArgumentException(); +#endif + } + + if( !std::isfinite( strokeAttributes.MiterLimit ) || + strokeAttributes.MiterLimit < 0.0 ) + { +#if OSL_DEBUG_LEVEL > 0 + throw lang::IllegalArgumentException( + OUString::createFromAscii(pStr) + + ": verifyInput(): stroke attributes' MiterLimit value out of range (is " + + OUString::number(strokeAttributes.MiterLimit) + ")", + xIf, nArgPos ); +#else + throw lang::IllegalArgumentException(); +#endif + } + + VerifyDashValue aVerifyDashValue( pStr, xIf, nArgPos ); + for (auto const& aStrokeAttribute : strokeAttributes.DashArray) + aVerifyDashValue( aStrokeAttribute ); + + for (auto const& aStrokeAttribute : strokeAttributes.LineArray) + aVerifyDashValue( aStrokeAttribute ); + + if( strokeAttributes.StartCapType < rendering::PathCapType::BUTT || + strokeAttributes.StartCapType > rendering::PathCapType::SQUARE ) + { +#if OSL_DEBUG_LEVEL > 0 + throw lang::IllegalArgumentException( + OUString::createFromAscii(pStr) + + ": verifyInput(): stroke attributes' StartCapType value is out of range (" + + OUString::number(sal::static_int_cast(strokeAttributes.StartCapType)) + + " not known)", + xIf, nArgPos ); +#else + throw lang::IllegalArgumentException(); +#endif + } + + if( strokeAttributes.EndCapType < rendering::PathCapType::BUTT || + strokeAttributes.EndCapType > rendering::PathCapType::SQUARE ) + { +#if OSL_DEBUG_LEVEL > 0 + throw lang::IllegalArgumentException( + OUString::createFromAscii(pStr) + + ": verifyInput(): stroke attributes' StartCapType value is out of range (" + + OUString::number(sal::static_int_cast(strokeAttributes.EndCapType)) + + " not known)", + xIf, nArgPos ); +#else + throw lang::IllegalArgumentException(); +#endif + } + + if( !(strokeAttributes.JoinType < rendering::PathJoinType::NONE || + strokeAttributes.JoinType > rendering::PathJoinType::BEVEL) ) + return; + +#if OSL_DEBUG_LEVEL > 0 + throw lang::IllegalArgumentException( + OUString::createFromAscii(pStr) + + ": verifyInput(): stroke attributes' JoinType value is out of range (" + + OUString::number(sal::static_int_cast(strokeAttributes.JoinType)) + + " not known)", + xIf, nArgPos ); +#else + throw lang::IllegalArgumentException(); +#endif + } + + void verifyInput( const rendering::IntegerBitmapLayout& bitmapLayout, + const char* pStr, + const uno::Reference< uno::XInterface >& xIf, + ::sal_Int16 nArgPos ) + { + if( bitmapLayout.ScanLines < 0 ) + { +#if OSL_DEBUG_LEVEL > 0 + throw lang::IllegalArgumentException( + OUString::createFromAscii(pStr) + + ": verifyInput(): bitmap layout's ScanLines is negative", + xIf, nArgPos ); +#else + (void)pStr; (void)xIf; (void)nArgPos; + throw lang::IllegalArgumentException(); +#endif + } + + if( bitmapLayout.ScanLineBytes < 0 ) + { +#if OSL_DEBUG_LEVEL > 0 + throw lang::IllegalArgumentException( + OUString::createFromAscii(pStr) + + ": verifyInput(): bitmap layout's ScanLineBytes is negative", + xIf, nArgPos ); +#else + throw lang::IllegalArgumentException(); +#endif + } + + if( !bitmapLayout.ColorSpace.is() ) + { +#if OSL_DEBUG_LEVEL > 0 + throw lang::IllegalArgumentException( + OUString::createFromAscii(pStr) + + ": verifyInput(): bitmap layout's ColorSpace is invalid", + xIf, nArgPos ); +#else + throw lang::IllegalArgumentException(); +#endif + } + if( bitmapLayout.ColorSpace->getBitsPerPixel() < 0 ) + { +#if OSL_DEBUG_LEVEL > 0 + throw lang::IllegalArgumentException( + OUString::createFromAscii(pStr) + + ": verifyInput(): bitmap layout's ColorSpace getBitsPerPixel() is negative", + xIf, nArgPos ); +#else + throw lang::IllegalArgumentException(); +#endif + } + + if( !(bitmapLayout.ColorSpace->getEndianness() < util::Endianness::LITTLE || + bitmapLayout.ColorSpace->getEndianness() > util::Endianness::BIG) ) + return; + +#if OSL_DEBUG_LEVEL > 0 + throw lang::IllegalArgumentException( + OUString::createFromAscii(pStr) + + ": verifyInput(): bitmap layout's ColorSpace getEndianness() value is out of range (" + + OUString::number(sal::static_int_cast(bitmapLayout.ColorSpace->getEndianness())) + + " not known)", + xIf, nArgPos ); +#else + throw lang::IllegalArgumentException(); +#endif + } + + void verifyInput( const rendering::FontRequest& fontRequest, + const char* pStr, + const uno::Reference< uno::XInterface >& xIf, + ::sal_Int16 nArgPos ) + { + verifyInput( fontRequest.FontDescription, + pStr, xIf, nArgPos ); + + if( !std::isfinite( fontRequest.CellSize ) ) + { +#if OSL_DEBUG_LEVEL > 0 + throw lang::IllegalArgumentException( + OUString::createFromAscii(pStr) + + ": verifyInput(): font request's CellSize value contains infinite or NAN", + xIf, nArgPos ); +#else + throw lang::IllegalArgumentException(); +#endif + } + + if( !std::isfinite( fontRequest.ReferenceAdvancement ) ) + { +#if OSL_DEBUG_LEVEL > 0 + throw lang::IllegalArgumentException( + OUString::createFromAscii(pStr) + + ": verifyInput(): font request's ReferenceAdvancement value contains infinite or NAN", + xIf, nArgPos ); +#else + throw lang::IllegalArgumentException(); +#endif + } + + if( fontRequest.CellSize != 0.0 && + fontRequest.ReferenceAdvancement != 0.0 ) + { +#if OSL_DEBUG_LEVEL > 0 + throw lang::IllegalArgumentException( + OUString::createFromAscii(pStr) + + ": verifyInput(): font request's CellSize and ReferenceAdvancement are mutually exclusive, one of them must be 0.0", + xIf, nArgPos ); +#else + throw lang::IllegalArgumentException(); +#endif + } + } + + void verifyIndexRange( const geometry::IntegerRectangle2D& rect, + const geometry::IntegerSize2D& size ) + { + const ::basegfx::B2IRange aRect( + ::basegfx::unotools::b2IRectangleFromIntegerRectangle2D( + rect ) ); + + if( aRect.getMinX() < 0 || + aRect.getMaxX() > size.Width || + aRect.getMinY() < 0 || + aRect.getMaxY() > size.Height ) + { + throw css::lang::IndexOutOfBoundsException(); + } + } + + void verifyIndexRange( const geometry::IntegerPoint2D& pos, + const geometry::IntegerSize2D& size ) + { + if( pos.X < 0 || + pos.X > size.Width || + pos.Y < 0 || + pos.Y > size.Height ) + { + throw css::lang::IndexOutOfBoundsException(); + } + } + + void verifyBitmapSize( const geometry::IntegerSize2D& size, + const char* pStr, + const uno::Reference< uno::XInterface >& xIf ) + { + if( size.Width <= 0 ) + { +#if OSL_DEBUG_LEVEL > 0 + throw lang::IllegalArgumentException( + OUString::createFromAscii(pStr) + + ": verifyBitmapSize(): size has 0 or negative width (value: " + + OUString::number(size.Width) + ")", + xIf, 0 ); +#else + (void)pStr; (void)xIf; + throw lang::IllegalArgumentException(); +#endif + } + + if( size.Height > 0 ) + return; + +#if OSL_DEBUG_LEVEL > 0 + throw lang::IllegalArgumentException( + OUString::createFromAscii(pStr) + + ": verifyBitmapSize(): size has 0 or negative height (value: " + + OUString::number(size.Height) + + ")", + xIf, 0 ); +#else + throw lang::IllegalArgumentException(); +#endif + } + + void verifySpriteSize( const geometry::RealSize2D& size, + const char* pStr, + const uno::Reference< uno::XInterface >& xIf ) + { + if( size.Width <= 0.0 ) + { +#if OSL_DEBUG_LEVEL > 0 + throw lang::IllegalArgumentException( + OUString::createFromAscii(pStr) + + ": verifySpriteSize(): size has 0 or negative width (value: " + + OUString::number(size.Width) + ")", + xIf, 0 ); +#else + (void)pStr; (void)xIf; + throw lang::IllegalArgumentException(); +#endif + } + + if( size.Height <= 0.0 ) + { +#if OSL_DEBUG_LEVEL > 0 + throw lang::IllegalArgumentException( + OUString::createFromAscii(pStr) + + ": verifySpriteSize(): size has 0 or negative height (value: " + + OUString::number(size.Height) + ")", + xIf, 0 ); +#else + throw lang::IllegalArgumentException(); +#endif + } + } + + +} // namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ -- cgit v1.2.3