diff options
Diffstat (limited to 'canvas/source')
166 files changed, 34009 insertions, 0 deletions
diff --git a/canvas/source/cairo/cairo_cachedbitmap.cxx b/canvas/source/cairo/cairo_cachedbitmap.cxx new file mode 100644 index 0000000000..e548778b2f --- /dev/null +++ b/canvas/source/cairo/cairo_cachedbitmap.cxx @@ -0,0 +1,77 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <com/sun/star/rendering/XCanvas.hpp> +#include <com/sun/star/rendering/RepaintResult.hpp> +#include <utility> +#include <comphelper/diagnose_ex.hxx> + +#include "cairo_cachedbitmap.hxx" +#include "cairo_repainttarget.hxx" + + +using namespace ::cairo; +using namespace ::com::sun::star; + +namespace cairocanvas +{ + CachedBitmap::CachedBitmap( SurfaceSharedPtr pSurface, + const rendering::ViewState& rUsedViewState, + rendering::RenderState aUsedRenderState, + const uno::Reference< rendering::XCanvas >& rTarget ) : + CachedPrimitiveBase( rUsedViewState, rTarget ), + mpSurface(std::move( pSurface )), + maRenderState(std::move( aUsedRenderState )) + {} + + void CachedBitmap::disposing(std::unique_lock<std::mutex>& rGuard) + { + mpSurface.reset(); + CachedPrimitiveBase::disposing(rGuard); + } + + ::sal_Int8 CachedBitmap::doRedraw( const rendering::ViewState& rNewState, + const rendering::ViewState& /*rOldState*/, + const uno::Reference< rendering::XCanvas >& rTargetCanvas, + bool bSameViewTransform ) + { + ENSURE_OR_THROW( bSameViewTransform, + "CachedBitmap::doRedraw(): base called with changed view transform " + "(told otherwise during construction)" ); + + RepaintTarget* pTarget = dynamic_cast< RepaintTarget* >(rTargetCanvas.get()); + + ENSURE_OR_THROW( pTarget, + "CachedBitmap::redraw(): cannot cast target to RepaintTarget" ); + + if( !pTarget->repaint( mpSurface, + rNewState, + maRenderState ) ) + { + // target failed to repaint + return rendering::RepaintResult::FAILED; + } + + return rendering::RepaintResult::REDRAWN; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/cairo/cairo_cachedbitmap.hxx b/canvas/source/cairo/cairo_cachedbitmap.hxx new file mode 100644 index 0000000000..d7f4c58ef6 --- /dev/null +++ b/canvas/source/cairo/cairo_cachedbitmap.hxx @@ -0,0 +1,58 @@ +/* -*- 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 . + */ + +#pragma once + +#include <base/cachedprimitivebase.hxx> +#include <com/sun/star/rendering/RenderState.hpp> + +#include <vcl/cairo.hxx> + +/* Definition of CachedBitmap class */ + +namespace cairocanvas +{ + class CachedBitmap : public ::canvas::CachedPrimitiveBase + { + public: + + /** Create an XCachedPrimitive for given GraphicObject + */ + CachedBitmap( ::cairo::SurfaceSharedPtr pSurface, + const css::rendering::ViewState& rUsedViewState, + css::rendering::RenderState aUsedRenderState, + const css::uno::Reference< css::rendering::XCanvas >& rTarget ); + + /// Dispose all internal references + virtual void disposing(std::unique_lock<std::mutex>& rGuard) override; + + private: + virtual ::sal_Int8 doRedraw( const css::rendering::ViewState& rNewState, + const css::rendering::ViewState& rOldState, + const css::uno::Reference< + css::rendering::XCanvas >& rTargetCanvas, + bool bSameViewTransform ) override; + + + ::cairo::SurfaceSharedPtr mpSurface; + const css::rendering::RenderState maRenderState; + }; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/cairo/cairo_canvas.cxx b/canvas/source/cairo/cairo_canvas.cxx new file mode 100644 index 0000000000..1a16a9f813 --- /dev/null +++ b/canvas/source/cairo/cairo_canvas.cxx @@ -0,0 +1,189 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> +#include <sal/log.hxx> + +#include <com/sun/star/awt/Rectangle.hpp> +#include <com/sun/star/lang/NoSupportException.hpp> +#include <osl/mutex.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <vcl/sysdata.hxx> +#include <vcl/skia/SkiaHelper.hxx> +#include <cppuhelper/supportsservice.hxx> + +#include "cairo_canvas.hxx" + +using namespace ::cairo; +using namespace ::com::sun::star; + +namespace cairocanvas +{ + Canvas::Canvas( const uno::Sequence< uno::Any >& aArguments, + const uno::Reference< uno::XComponentContext >& /*rxContext*/ ) : + maArguments(aArguments) + { + } + + void Canvas::initialize() + { + // #i64742# Only perform initialization when not in probe mode + if( !maArguments.hasElements() ) + return; + + assert( !SkiaHelper::isVCLSkiaEnabled() ); + + /* maArguments: + 0: ptr to creating instance (Window or VirtualDevice) + 1: current bounds of creating instance + 2: bool, denoting always on top state for Window (always false for VirtualDevice) + 3: XWindow for creating Window (or empty for VirtualDevice) + 4: SystemGraphicsData as a streamed Any + */ + SAL_INFO("canvas.cairo","Canvas created " << this); + + ENSURE_ARG_OR_THROW( maArguments.getLength() >= 5 && + maArguments[0].getValueTypeClass() == uno::TypeClass_HYPER && + maArguments[4].getValueTypeClass() == uno::TypeClass_SEQUENCE, + "Canvas::initialize: wrong number of arguments, or wrong types" ); + + // We expect a single Any here, containing a pointer to a valid + // VCL output device, on which to output (mostly needed for text) + sal_Int64 nPtr = 0; + maArguments[0] >>= nPtr; + OutputDevice* pOutDev = reinterpret_cast<OutputDevice*>(nPtr); + ENSURE_ARG_OR_THROW( pOutDev != nullptr, + "Canvas::initialize: invalid OutDev pointer" ); + + awt::Rectangle aBounds; + maArguments[1] >>= aBounds; + + uno::Sequence<sal_Int8> aSeq; + maArguments[4] >>= aSeq; + + const SystemGraphicsData* pSysData=reinterpret_cast<const SystemGraphicsData*>(aSeq.getConstArray()); + if( !pSysData || !pSysData->nSize ) + throw lang::NoSupportException( "Passed SystemGraphicsData invalid!" ); + + bool bHasCairo = pOutDev->SupportsCairo(); + ENSURE_ARG_OR_THROW(bHasCairo, "SpriteCanvas::SpriteCanvas: No Cairo capability"); + + // setup helper + maDeviceHelper.init( *this, *pOutDev ); + + maCanvasHelper.init( basegfx::B2ISize(aBounds.Width, aBounds.Height), *this, this ); + + // forward surface to render on to canvashelper + maCanvasHelper.setSurface( maDeviceHelper.getSurface(), false ); + + maArguments.realloc(0); + } + + Canvas::~Canvas() + { + SAL_INFO("canvas.cairo", "CairoCanvas destroyed" ); + } + + void Canvas::disposeThis() + { + ::osl::MutexGuard aGuard( m_aMutex ); + + // forward to parent + CanvasBaseT::disposeThis(); + } + + OUString SAL_CALL Canvas::getServiceName( ) + { + return "com.sun.star.rendering.Canvas.Cairo"; + } + + // XServiceInfo + sal_Bool Canvas::supportsService(const OUString& sServiceName) + { + return cppu::supportsService(this, sServiceName); + + } + OUString Canvas::getImplementationName() + { + return "com.sun.star.comp.rendering.Canvas.Cairo"; + } + css::uno::Sequence< OUString > Canvas::getSupportedServiceNames() + { + return { getServiceName() }; + } + + bool Canvas::repaint( const SurfaceSharedPtr& pSurface, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState ) + { + return maCanvasHelper.repaint( pSurface, viewState, renderState ); + } + + SurfaceSharedPtr Canvas::getSurface() + { + return maDeviceHelper.getSurface(); + } + + SurfaceSharedPtr Canvas::createSurface( const ::basegfx::B2ISize& rSize, int aContent ) + { + return maDeviceHelper.createSurface( rSize, aContent ); + } + + SurfaceSharedPtr Canvas::createSurface( ::Bitmap& rBitmap ) + { + SurfaceSharedPtr pSurface; + + BitmapSystemData aData; + if( rBitmap.GetSystemData( aData ) ) { + const Size& rSize = rBitmap.GetSizePixel(); + + pSurface = maDeviceHelper.createSurface( aData, rSize ); + } + + return pSurface; + } + + SurfaceSharedPtr Canvas::changeSurface() + { + // non-modifiable surface here + return SurfaceSharedPtr(); + } + + OutputDevice* Canvas::getOutputDevice() + { + return maDeviceHelper.getOutputDevice(); + } +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +com_sun_star_comp_rendering_Canvas_Cairo_get_implementation( + css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const& args) +{ + rtl::Reference<cairocanvas::Canvas> p = new cairocanvas::Canvas(args, context); + try { + p->initialize(); + } catch (css::uno::Exception&) { + p->dispose(); + throw; + } + return cppu::acquire(p.get()); +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/cairo/cairo_canvas.hxx b/canvas/source/cairo/cairo_canvas.hxx new file mode 100644 index 0000000000..0c41a8a5c0 --- /dev/null +++ b/canvas/source/cairo/cairo_canvas.hxx @@ -0,0 +1,142 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/lang/XServiceName.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/util/XUpdatable.hpp> +#include <com/sun/star/rendering/XIntegerBitmap.hpp> +#include <com/sun/star/rendering/XGraphicDevice.hpp> +#include <com/sun/star/rendering/XBitmapCanvas.hpp> + +#include <cppuhelper/compbase.hxx> +#include <comphelper/uno3.hxx> + +#include <base/basemutexhelper.hxx> +#include <base/bitmapcanvasbase.hxx> +#include <base/graphicdevicebase.hxx> +#include <base/integerbitmapbase.hxx> + +#include "cairo_canvashelper.hxx" +#include "cairo_devicehelper.hxx" +#include "cairo_repainttarget.hxx" +#include "cairo_surfaceprovider.hxx" + +namespace cairocanvas +{ + typedef ::cppu::WeakComponentImplHelper< css::rendering::XBitmapCanvas, + css::rendering::XIntegerBitmap, + css::rendering::XGraphicDevice, + css::lang::XMultiServiceFactory, + css::util::XUpdatable, + css::beans::XPropertySet, + css::lang::XServiceName, + css::lang::XServiceInfo > GraphicDeviceBase_Base; + typedef ::canvas::GraphicDeviceBase< ::canvas::BaseMutexHelper< GraphicDeviceBase_Base >, + DeviceHelper, + ::osl::MutexGuard, + ::cppu::OWeakObject > CanvasBase_Base; + + /** Mixin SurfaceProvider + + Have to mixin the SurfaceProvider before deriving from + ::canvas::CanvasBase, as this template should already + implement some of those interface methods. + + The reason why this appears kinda convoluted is the fact that + we cannot specify non-IDL types as WeakComponentImplHelper + template args, and furthermore, don't want to derive + ::canvas::CanvasBase directly from + SurfaceProvider (because derivees of + ::canvas::CanvasBase have to explicitly forward the + XInterface methods (e.g. via DECLARE_UNO3_AGG_DEFAULTS) + anyway). + */ + class CanvasBaseSurfaceProvider_Base : public CanvasBase_Base, + public SurfaceProvider + { + }; + + typedef ::canvas::IntegerBitmapBase< + canvas::BitmapCanvasBase2< + CanvasBaseSurfaceProvider_Base, + CanvasHelper, + ::osl::MutexGuard, + ::cppu::OWeakObject> > CanvasBaseT; + + /** Product of this component's factory. + + The Canvas object combines the actual Window canvas with + the XGraphicDevice interface. This is because there's a + one-to-one relation between them, anyway, since each window + can have exactly one canvas and one associated + XGraphicDevice. And to avoid messing around with circular + references, this is implemented as one single object. + */ + class Canvas : public CanvasBaseT, + public RepaintTarget + { + public: + Canvas( const css::uno::Sequence< css::uno::Any >& aArguments, + const css::uno::Reference< css::uno::XComponentContext >& rxContext ); + + void initialize(); + + /// For resource tracking + virtual ~Canvas() override; + + /// Dispose all internal references + virtual void disposeThis() override; + + // Forwarding the XComponent implementation to the + // cppu::ImplHelper templated base + // Classname Base doing refcounting Base implementing the XComponent interface + // | | | + // V V V + DECLARE_UNO3_XCOMPONENT_AGG_DEFAULTS( Canvas, GraphicDeviceBase_Base, ::cppu::WeakComponentImplHelperBase ) + + // XServiceName + virtual OUString SAL_CALL getServiceName( ) override; + + // XServiceInfo + virtual sal_Bool SAL_CALL supportsService(const OUString& sServiceName) override; + virtual OUString SAL_CALL getImplementationName() override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override; + + // RepaintTarget + virtual bool repaint( const ::cairo::SurfaceSharedPtr& pSurface, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState ) override; + + // SurfaceProvider + virtual ::cairo::SurfaceSharedPtr getSurface() override; + virtual ::cairo::SurfaceSharedPtr createSurface( const ::basegfx::B2ISize& rSize, int aContent ) override; + virtual ::cairo::SurfaceSharedPtr createSurface( ::Bitmap& rBitmap ) override; + virtual ::cairo::SurfaceSharedPtr changeSurface() override; + virtual OutputDevice* getOutputDevice() override; + + private: + css::uno::Sequence< css::uno::Any > maArguments; + }; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/cairo/cairo_canvasbitmap.cxx b/canvas/source/cairo/cairo_canvasbitmap.cxx new file mode 100644 index 0000000000..9f18be8082 --- /dev/null +++ b/canvas/source/cairo/cairo_canvasbitmap.cxx @@ -0,0 +1,163 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> +#include <sal/log.hxx> + +#include <cppuhelper/supportsservice.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <utility> +#include <vcl/bitmapex.hxx> +#include <vcl/BitmapTools.hxx> + +#include <cairo.h> + +#include "cairo_canvasbitmap.hxx" + +using namespace ::cairo; +using namespace ::com::sun::star; + +namespace cairocanvas +{ + CanvasBitmap::CanvasBitmap( const ::basegfx::B2ISize& rSize, + SurfaceProviderRef rSurfaceProvider, + rendering::XGraphicDevice* pDevice, + bool bHasAlpha ) : + mpSurfaceProvider(std::move( rSurfaceProvider )), + maSize(rSize), + mbHasAlpha(bHasAlpha) + { + ENSURE_OR_THROW( mpSurfaceProvider.is(), + "CanvasBitmap::CanvasBitmap(): Invalid surface or device" ); + + SAL_INFO( + "canvas.cairo", + "bitmap size: " << rSize.getWidth() << "x" << rSize.getHeight()); + + mpBufferSurface = mpSurfaceProvider->createSurface( rSize, bHasAlpha ? CAIRO_CONTENT_COLOR_ALPHA : CAIRO_CONTENT_COLOR ); + mpBufferCairo = mpBufferSurface->getCairo(); + + maCanvasHelper.init( rSize, *mpSurfaceProvider, pDevice ); + maCanvasHelper.setSurface( mpBufferSurface, bHasAlpha ); + + // clear bitmap to 100% transparent + maCanvasHelper.clear(); + } + + void CanvasBitmap::disposeThis() + { + mpSurfaceProvider.clear(); + + mpBufferCairo.reset(); + mpBufferSurface.reset(); + + // forward to parent + CanvasBitmap_Base::disposeThis(); + } + + SurfaceSharedPtr CanvasBitmap::getSurface() + { + return mpBufferSurface; + } + + SurfaceSharedPtr CanvasBitmap::createSurface( const ::basegfx::B2ISize& rSize, int aContent ) + { + return mpSurfaceProvider->createSurface(rSize,aContent); + } + + SurfaceSharedPtr CanvasBitmap::createSurface( ::Bitmap& rBitmap ) + { + return mpSurfaceProvider->createSurface(rBitmap); + } + + SurfaceSharedPtr CanvasBitmap::changeSurface() + { + // non-modifiable surface here + return SurfaceSharedPtr(); + } + + OutputDevice* CanvasBitmap::getOutputDevice() + { + return mpSurfaceProvider->getOutputDevice(); + } + + bool CanvasBitmap::repaint( const SurfaceSharedPtr& pSurface, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState ) + { + return maCanvasHelper.repaint( pSurface, viewState, renderState ); + } + + uno::Any SAL_CALL CanvasBitmap::getFastPropertyValue( sal_Int32 nHandle ) + { + uno::Any aRV( sal_Int32(0) ); + // 0 ... get BitmapEx + // 1 ... get Pixbuf with bitmap RGB content + // 2 ... return nothing (empty Any) + switch( nHandle ) + { + case 0: + { + aRV <<= reinterpret_cast<sal_Int64>( nullptr ); + if ( !mbHasAlpha ) + break; + + BitmapEx* pBitmapEx = vcl::bitmap::CreateFromCairoSurface( + ::Size( maSize.getWidth(), maSize.getHeight() ), + getSurface()->getCairoSurface().get()); + if (pBitmapEx) + aRV <<= reinterpret_cast<sal_Int64>( pBitmapEx ); + + break; + } + case 1: + { + aRV = getOutputDevice()->GetNativeSurfaceHandle(mpBufferSurface, maSize); + break; + } + case 2: + { + // Always return nothing - for the RGB surface support. + // Alpha code paths go via the above case 0. + aRV = uno::Any(); + break; + } + } + + return aRV; + } + + OUString SAL_CALL CanvasBitmap::getImplementationName( ) + { + return "CairoCanvas.CanvasBitmap"; + } + + sal_Bool SAL_CALL CanvasBitmap::supportsService( const OUString& ServiceName ) + { + return cppu::supportsService( this, ServiceName ); + } + + uno::Sequence< OUString > SAL_CALL CanvasBitmap::getSupportedServiceNames( ) + { + return { "com.sun.star.rendering.CanvasBitmap" }; + } + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/cairo/cairo_canvasbitmap.hxx b/canvas/source/cairo/cairo_canvasbitmap.hxx new file mode 100644 index 0000000000..f237182102 --- /dev/null +++ b/canvas/source/cairo/cairo_canvasbitmap.hxx @@ -0,0 +1,125 @@ +/* -*- 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 . + */ + +#pragma once + +#include <cppuhelper/compbase.hxx> + +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/rendering/XBitmapCanvas.hpp> +#include <com/sun/star/rendering/XIntegerBitmap.hpp> +#include <com/sun/star/beans/XFastPropertySet.hpp> +#include <comphelper/uno3.hxx> + +#include <base/bitmapcanvasbase.hxx> +#include <base/basemutexhelper.hxx> +#include <base/integerbitmapbase.hxx> + +#include "cairo_canvashelper.hxx" +#include "cairo_repainttarget.hxx" + + +/* Definition of CanvasBitmap class */ + +namespace cairocanvas +{ + typedef ::cppu::WeakComponentImplHelper< css::rendering::XBitmapCanvas, + css::rendering::XIntegerBitmap, + css::lang::XServiceInfo, + css::beans::XFastPropertySet > CanvasBitmapBase_Base; + class CanvasBitmapSpriteSurface_Base : + public ::canvas::BaseMutexHelper<CanvasBitmapBase_Base>, + public SurfaceProvider + { + }; + + typedef ::canvas::IntegerBitmapBase< + canvas::BitmapCanvasBase2< + CanvasBitmapSpriteSurface_Base, + CanvasHelper, + ::osl::MutexGuard, + ::cppu::OWeakObject> > CanvasBitmap_Base; + + class CanvasBitmap : public CanvasBitmap_Base, + public RepaintTarget + { + public: + /** Create a canvas bitmap for the given surface + + @param rSize + Size of the bitmap + + @param rDevice + Reference device, with which bitmap should be compatible + */ + CanvasBitmap( const ::basegfx::B2ISize& rSize, + SurfaceProviderRef rDevice, + css::rendering::XGraphicDevice* pDevice, + bool bHasAlpha ); + + /// Dispose all internal references + virtual void disposeThis() override; + + // Forwarding the XComponent implementation to the + // cppu::ImplHelper templated base + // Classname Base doing refcounting Base implementing the XComponent interface + // | | | + // V V V + DECLARE_UNO3_XCOMPONENT_AGG_DEFAULTS( CanvasBitmap, CanvasBitmapBase_Base, ::cppu::WeakComponentImplHelperBase ) + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName( ) override; + virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames( ) override; + + // SurfaceProvider + virtual ::cairo::SurfaceSharedPtr getSurface() override; + virtual ::cairo::SurfaceSharedPtr createSurface( const ::basegfx::B2ISize& rSize, int aContent ) override; + virtual ::cairo::SurfaceSharedPtr createSurface( ::Bitmap& rBitmap ) override; + virtual ::cairo::SurfaceSharedPtr changeSurface() override; + virtual OutputDevice* getOutputDevice() override; + + // RepaintTarget + virtual bool repaint( const ::cairo::SurfaceSharedPtr& pSurface, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState ) override; + + // XFastPropertySet + // used to retrieve BitmapEx pointer or X Pixmap handles for this bitmap + // handle values have these meanings: + // 0 ... get pointer to BitmapEx + // 1 ... get X pixmap handle to rgb content + // 2 ... FIXME: leftover? ever called with this? always returns nothing (empty Any) + // returned any contains either BitmapEx pointer or array of two Any value + // 1st a bool value: true - free the pixmap after used by XFreePixmap, false do nothing, the pixmap is used internally in the canvas + // 2nd the pixmap handle (sal_Int64) + virtual css::uno::Any SAL_CALL getFastPropertyValue(sal_Int32 nHandle) override; + virtual void SAL_CALL setFastPropertyValue(sal_Int32, const css::uno::Any&) override {} + + private: + SurfaceProviderRef mpSurfaceProvider; + ::cairo::SurfaceSharedPtr mpBufferSurface; + ::cairo::CairoSharedPtr mpBufferCairo; + + const ::basegfx::B2ISize maSize; + const bool mbHasAlpha; + }; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/cairo/cairo_canvascustomsprite.cxx b/canvas/source/cairo/cairo_canvascustomsprite.cxx new file mode 100644 index 0000000000..f7fffb6e50 --- /dev/null +++ b/canvas/source/cairo/cairo_canvascustomsprite.cxx @@ -0,0 +1,151 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> +#include <sal/log.hxx> + +#include <basegfx/point/b2dpoint.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <comphelper/diagnose_ex.hxx> + +#include <canvas/canvastools.hxx> +#include <cairo.h> + +#include "cairo_canvascustomsprite.hxx" +#include "cairo_spritecanvas.hxx" + + +using namespace ::cairo; +using namespace ::com::sun::star; + +namespace cairocanvas +{ + CanvasCustomSprite::CanvasCustomSprite( const css::geometry::RealSize2D& rSpriteSize, + const SpriteCanvasRef& rRefDevice ) : + mpSpriteCanvas( rRefDevice ), + maSize( ::canvas::tools::roundUp( rSpriteSize.Width ), + ::canvas::tools::roundUp( rSpriteSize.Height ) ) + { + ENSURE_OR_THROW( rRefDevice, + "CanvasCustomSprite::CanvasCustomSprite(): Invalid sprite canvas" ); + + SAL_INFO( "canvas.cairo", "sprite size: " << ::canvas::tools::roundUp( rSpriteSize.Width ) << ", " << ::canvas::tools::roundUp( rSpriteSize.Height )); + + mpBufferSurface = mpSpriteCanvas->createSurface( maSize, CAIRO_CONTENT_COLOR_ALPHA ); + + maCanvasHelper.init( maSize, + *rRefDevice, + rRefDevice.get() ); + maCanvasHelper.setSurface( mpBufferSurface, true ); + + maSpriteHelper.init( rSpriteSize, + rRefDevice ); + maSpriteHelper.setSurface( mpBufferSurface ); + + // clear sprite to 100% transparent + maCanvasHelper.clear(); + } + + void CanvasCustomSprite::disposeThis() + { + ::osl::MutexGuard aGuard( m_aMutex ); + + mpSpriteCanvas.clear(); + mpBufferSurface.reset(); + + // forward to parent + CanvasCustomSpriteBaseT::disposeThis(); + } + + void CanvasCustomSprite::redraw( const CairoSharedPtr& pCairo, + bool bBufferedUpdate ) const + { + ::osl::MutexGuard aGuard( m_aMutex ); + + redraw( pCairo, maSpriteHelper.getPosPixel(), bBufferedUpdate ); + } + + void CanvasCustomSprite::redraw( const CairoSharedPtr& pCairo, + const ::basegfx::B2DPoint& rOrigOutputPos, + bool bBufferedUpdate ) const + { + ::osl::MutexGuard aGuard( m_aMutex ); + + maSpriteHelper.redraw( pCairo, + rOrigOutputPos, + mbSurfaceDirty, + bBufferedUpdate ); + + mbSurfaceDirty = false; + } + + bool CanvasCustomSprite::repaint( const SurfaceSharedPtr& pSurface, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState ) + { + return maCanvasHelper.repaint( pSurface, viewState, renderState ); + } + + SurfaceSharedPtr CanvasCustomSprite::getSurface() + { + return mpBufferSurface; + } + + SurfaceSharedPtr CanvasCustomSprite::createSurface( const ::basegfx::B2ISize& rSize, int aContent ) + { + return mpSpriteCanvas->createSurface(rSize,aContent); + } + + SurfaceSharedPtr CanvasCustomSprite::createSurface( ::Bitmap& rBitmap ) + { + return mpSpriteCanvas->createSurface(rBitmap); + } + + SurfaceSharedPtr CanvasCustomSprite::changeSurface() + { + SAL_INFO( "canvas.cairo", "replacing sprite background surface"); + + mpBufferSurface = mpSpriteCanvas->createSurface( maSize, CAIRO_CONTENT_COLOR ); + maSpriteHelper.setSurface( mpBufferSurface ); + + return mpBufferSurface; + } + + OutputDevice* CanvasCustomSprite::getOutputDevice() + { + return mpSpriteCanvas->getOutputDevice(); + } + + OUString SAL_CALL CanvasCustomSprite::getImplementationName() + { + return "CairoCanvas.CanvasCustomSprite"; + } + + sal_Bool SAL_CALL CanvasCustomSprite::supportsService( const OUString& ServiceName ) + { + return cppu::supportsService( this, ServiceName ); + } + + uno::Sequence< OUString > SAL_CALL CanvasCustomSprite::getSupportedServiceNames() + { + return { "com.sun.star.rendering.CanvasCustomSprite" }; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/cairo/cairo_canvascustomsprite.hxx b/canvas/source/cairo/cairo_canvascustomsprite.hxx new file mode 100644 index 0000000000..ffe766ab09 --- /dev/null +++ b/canvas/source/cairo/cairo_canvascustomsprite.hxx @@ -0,0 +1,145 @@ +/* -*- 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 . + */ + +#pragma once + +#include <cppuhelper/compbase.hxx> +#include <comphelper/uno3.hxx> + +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/rendering/XBitmapCanvas.hpp> +#include <com/sun/star/rendering/XCustomSprite.hpp> +#include <com/sun/star/rendering/XIntegerBitmap.hpp> + +#include <basegfx/point/b2dpoint.hxx> + +#include <base/basemutexhelper.hxx> +#include <base/canvascustomspritebase.hxx> + +#include <vcl/cairo.hxx> + +#include "cairo_sprite.hxx" +#include "cairo_canvashelper.hxx" +#include "cairo_repainttarget.hxx" +#include "cairo_spritehelper.hxx" +#include "cairo_spritecanvas.hxx" + + +namespace cairocanvas +{ + typedef ::cppu::WeakComponentImplHelper< css::rendering::XCustomSprite, + css::rendering::XBitmapCanvas, + css::rendering::XIntegerBitmap, + css::lang::XServiceInfo > CanvasCustomSpriteBase_Base; + /** Mixin Sprite + + Have to mixin the Sprite interface before deriving from + ::canvas::CanvasCustomSpriteBase, as this template should + already implement some of those interface methods. + + The reason why this appears kinda convoluted is the fact that + we cannot specify non-IDL types as WeakComponentImplHelper + template args, and furthermore, don't want to derive + ::canvas::CanvasCustomSpriteBase directly from + ::canvas::Sprite (because derivees of + ::canvas::CanvasCustomSpriteBase have to explicitly forward + the XInterface methods (e.g. via DECLARE_UNO3_AGG_DEFAULTS) + anyway). Basically, ::canvas::CanvasCustomSpriteBase should + remain a base class that provides implementation, not to + enforce any specific interface on its derivees. + */ + class CanvasCustomSpriteSpriteBase_Base : public ::canvas::BaseMutexHelper< CanvasCustomSpriteBase_Base >, + public Sprite, + public SurfaceProvider + { + }; + + typedef ::canvas::CanvasCustomSpriteBase< CanvasCustomSpriteSpriteBase_Base, + SpriteHelper, + CanvasHelper, + ::osl::MutexGuard, + ::cppu::OWeakObject > CanvasCustomSpriteBaseT; + + /* Definition of CanvasCustomSprite class */ + + class CanvasCustomSprite : public CanvasCustomSpriteBaseT, + public RepaintTarget + { + public: + /** Create a custom sprite + + @param rSpriteSize + Size of the sprite in pixel + + @param rRefDevice + Associated output device + + @param rSpriteCanvas + Target canvas + + @param rDevice + Target DX device + */ + CanvasCustomSprite( const css::geometry::RealSize2D& rSpriteSize, + const SpriteCanvasRef& rRefDevice ); + + virtual void disposeThis() override; + + // Forwarding the XComponent implementation to the + // cppu::ImplHelper templated base + // Classname Base doing refcount Base implementing the XComponent interface + // | | | + // V V V + DECLARE_UNO3_XCOMPONENT_AGG_DEFAULTS( CanvasCustomSprite, CanvasCustomSpriteBase_Base, ::cppu::WeakComponentImplHelperBase ) + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override; + + // Sprite + virtual void redraw( const ::cairo::CairoSharedPtr& pCairo, + bool bBufferedUpdate ) const override; + virtual void redraw( const ::cairo::CairoSharedPtr& pCairo, + const ::basegfx::B2DPoint& rOrigOutputPos, + bool bBufferedUpdate ) const override; + + // RepaintTarget + virtual bool repaint( const ::cairo::SurfaceSharedPtr& pSurface, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState ) override; + + // SurfaceProvider + virtual ::cairo::SurfaceSharedPtr getSurface() override; + virtual ::cairo::SurfaceSharedPtr createSurface( const ::basegfx::B2ISize& rSize, int aContent ) override; + virtual ::cairo::SurfaceSharedPtr createSurface( ::Bitmap& rBitmap ) override; + virtual ::cairo::SurfaceSharedPtr changeSurface() override; + virtual OutputDevice* getOutputDevice() override; + + private: + /** MUST hold here, too, since CanvasHelper only contains a + raw pointer (without refcounting) + */ + SpriteCanvasRef mpSpriteCanvas; + ::cairo::SurfaceSharedPtr mpBufferSurface; + ::basegfx::B2ISize maSize; + }; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/cairo/cairo_canvasfont.cxx b/canvas/source/cairo/cairo_canvasfont.cxx new file mode 100644 index 0000000000..2445f40885 --- /dev/null +++ b/canvas/source/cairo/cairo_canvasfont.cxx @@ -0,0 +1,161 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <basegfx/numeric/ftools.hxx> +#include <com/sun/star/rendering/PanoseProportion.hpp> +#include <cppuhelper/supportsservice.hxx> +#include <i18nlangtag/languagetag.hxx> +#include <rtl/math.hxx> +#include <utility> +#include <vcl/metric.hxx> + +#include <canvas/canvastools.hxx> + +#include "cairo_canvasfont.hxx" +#include "cairo_textlayout.hxx" + +using namespace ::com::sun::star; + +namespace cairocanvas +{ + + CanvasFont::CanvasFont( const rendering::FontRequest& rFontRequest, + const uno::Sequence< beans::PropertyValue >& rExtraFontProperties, + const geometry::Matrix2D& rFontMatrix, + SurfaceProviderRef rDevice ) : + maFont( vcl::Font( rFontRequest.FontDescription.FamilyName, + rFontRequest.FontDescription.StyleName, + Size( 0, ::basegfx::fround(rFontRequest.CellSize) ) ) ), + maFontRequest( rFontRequest ), + mpRefDevice(std::move( rDevice )), + mnEmphasisMark(0) + { + ::canvas::tools::extractExtraFontProperties(rExtraFontProperties, mnEmphasisMark); + + maFont->SetAlignment( ALIGN_BASELINE ); + maFont->SetCharSet( (rFontRequest.FontDescription.IsSymbolFont==css::util::TriState_YES) ? RTL_TEXTENCODING_SYMBOL : RTL_TEXTENCODING_UNICODE ); + maFont->SetVertical( rFontRequest.FontDescription.IsVertical==css::util::TriState_YES ); + + // TODO(F2): improve panose->vclenum conversion + maFont->SetWeight( static_cast<FontWeight>(rFontRequest.FontDescription.FontDescription.Weight) ); + maFont->SetItalic( (rFontRequest.FontDescription.FontDescription.Letterform<=8) ? ITALIC_NONE : ITALIC_NORMAL ); + maFont->SetPitch( + rFontRequest.FontDescription.FontDescription.Proportion == rendering::PanoseProportion::MONO_SPACED + ? PITCH_FIXED : PITCH_VARIABLE); + + maFont->SetLanguage( LanguageTag::convertToLanguageType( rFontRequest.Locale, false)); + + // adjust to stretched/shrunk font + if( ::rtl::math::approxEqual( rFontMatrix.m00, rFontMatrix.m11) ) + return; + + VclPtr<OutputDevice> pOutDev( mpRefDevice->getOutputDevice() ); + + if( !pOutDev ) + return; + + const bool bOldMapState( pOutDev->IsMapModeEnabled() ); + pOutDev->EnableMapMode(false); + + const Size aSize = pOutDev->GetFontMetric( *maFont ).GetFontSize(); + + const double fDividend( rFontMatrix.m10 + rFontMatrix.m11 ); + double fStretch = rFontMatrix.m00 + rFontMatrix.m01; + + if( !::basegfx::fTools::equalZero( fDividend) ) + fStretch /= fDividend; + + const tools::Long nNewWidth = ::basegfx::fround( aSize.Width() * fStretch ); + + maFont->SetAverageFontWidth( nNewWidth ); + + pOutDev->EnableMapMode(bOldMapState); + } + + void CanvasFont::disposing(std::unique_lock<std::mutex>& rGuard) + { + rGuard.unlock(); + { + SolarMutexGuard aGuard; + mpRefDevice.clear(); + } + rGuard.lock(); + } + + uno::Reference< rendering::XTextLayout > SAL_CALL CanvasFont::createTextLayout( const rendering::StringContext& aText, sal_Int8 nDirection, sal_Int64 nRandomSeed ) + { + SolarMutexGuard aGuard; + + if( !mpRefDevice.is() ) + return uno::Reference< rendering::XTextLayout >(); // we're disposed + + return new TextLayout( aText, + nDirection, + nRandomSeed, + Reference( this ), + mpRefDevice ); + } + + rendering::FontRequest SAL_CALL CanvasFont::getFontRequest( ) + { + return maFontRequest; + } + + rendering::FontMetrics SAL_CALL CanvasFont::getFontMetrics( ) + { + // TODO(F1) + return rendering::FontMetrics(); + } + + uno::Sequence< double > SAL_CALL CanvasFont::getAvailableSizes( ) + { + // TODO(F1) + return uno::Sequence< double >(); + } + + uno::Sequence< beans::PropertyValue > SAL_CALL CanvasFont::getExtraFontProperties( ) + { + // TODO(F1) + return uno::Sequence< beans::PropertyValue >(); + } + + OUString SAL_CALL CanvasFont::getImplementationName() + { + return "CairoCanvas::CanvasFont"; + } + + sal_Bool SAL_CALL CanvasFont::supportsService( const OUString& ServiceName ) + { + return cppu::supportsService( this, ServiceName ); + } + + uno::Sequence< OUString > SAL_CALL CanvasFont::getSupportedServiceNames() + { + return { "com.sun.star.rendering.CanvasFont" }; + } + + vcl::Font const & CanvasFont::getVCLFont() const + { + return *maFont; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/cairo/cairo_canvasfont.hxx b/canvas/source/cairo/cairo_canvasfont.hxx new file mode 100644 index 0000000000..d5e015006c --- /dev/null +++ b/canvas/source/cairo/cairo_canvasfont.hxx @@ -0,0 +1,84 @@ +/* -*- 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 . + */ + +#pragma once + +#include <comphelper/compbase.hxx> + +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/geometry/Matrix2D.hpp> +#include <com/sun/star/rendering/FontRequest.hpp> +#include <com/sun/star/rendering/XCanvasFont.hpp> + +#include <vcl/font.hxx> + +#include <vclwrapper.hxx> + +#include "cairo_surfaceprovider.hxx" + + +/* Definition of CanvasFont class */ + +namespace cairocanvas +{ + typedef ::comphelper::WeakComponentImplHelper< css::rendering::XCanvasFont, + css::lang::XServiceInfo > CanvasFont_Base; + + class CanvasFont : public CanvasFont_Base + { + public: + typedef rtl::Reference<CanvasFont> Reference; + /// make noncopyable + CanvasFont(const CanvasFont&) = delete; + const CanvasFont& operator=(const CanvasFont&) = delete; + + CanvasFont( const css::rendering::FontRequest& fontRequest, + const css::uno::Sequence< css::beans::PropertyValue >& extraFontProperties, + const css::geometry::Matrix2D& rFontMatrix, + SurfaceProviderRef rDevice ); + + /// Dispose all internal references + virtual void disposing(std::unique_lock<std::mutex>& rGuard) override; + + // XCanvasFont + virtual css::uno::Reference< css::rendering::XTextLayout > SAL_CALL createTextLayout( const css::rendering::StringContext& aText, sal_Int8 nDirection, sal_Int64 nRandomSeed ) override; + virtual css::rendering::FontRequest SAL_CALL getFontRequest( ) override; + virtual css::rendering::FontMetrics SAL_CALL getFontMetrics( ) override; + virtual css::uno::Sequence< double > SAL_CALL getAvailableSizes( ) override; + virtual css::uno::Sequence< css::beans::PropertyValue > SAL_CALL getExtraFontProperties( ) override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override; + + vcl::Font const & getVCLFont() const; + + sal_uInt32 getEmphasisMark() const { return mnEmphasisMark; } + + private: + ::canvas::vcltools::VCLObject<vcl::Font> maFont; + css::rendering::FontRequest maFontRequest; + SurfaceProviderRef mpRefDevice; + sal_uInt32 mnEmphasisMark; + }; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/cairo/cairo_canvashelper.cxx b/canvas/source/cairo/cairo_canvashelper.cxx new file mode 100644 index 0000000000..2eb13678ca --- /dev/null +++ b/canvas/source/cairo/cairo_canvashelper.cxx @@ -0,0 +1,2047 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> +#include <sal/log.hxx> + +#include <algorithm> +#include <tuple> + +#include <basegfx/point/b2dpoint.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/polygon/b2dpolypolygon.hxx> +#include <basegfx/utils/canvastools.hxx> +#include <basegfx/utils/keystoplerp.hxx> +#include <basegfx/utils/lerp.hxx> +#include <com/sun/star/rendering/ColorComponentTag.hpp> +#include <com/sun/star/rendering/ColorSpaceType.hpp> +#include <com/sun/star/rendering/CompositeOperation.hpp> +#include <com/sun/star/rendering/IntegerBitmapLayout.hpp> +#include <com/sun/star/rendering/PathCapType.hpp> +#include <com/sun/star/rendering/PathJoinType.hpp> +#include <com/sun/star/rendering/RenderingIntent.hpp> +#include <com/sun/star/rendering/TexturingMode.hpp> +#include <com/sun/star/rendering/XIntegerBitmapColorSpace.hpp> +#include <com/sun/star/util/Endianness.hpp> +#include <comphelper/sequence.hxx> +#include <cppuhelper/implbase.hxx> +#include <rtl/math.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <vcl/bitmapex.hxx> +#include <vcl/BitmapTools.hxx> +#include <vcl/canvastools.hxx> +#include <vcl/virdev.hxx> + +#include <canvas/canvastools.hxx> +#include <parametricpolypolygon.hxx> +#include <cairo.h> + +#include "cairo_cachedbitmap.hxx" +#include "cairo_canvasbitmap.hxx" +#include "cairo_canvashelper.hxx" + +using namespace ::cairo; +using namespace ::com::sun::star; + +namespace cairocanvas +{ + CanvasHelper::CanvasHelper() : + mpSurfaceProvider(nullptr), + mpDevice(nullptr), + mbHaveAlpha() + { + } + + void CanvasHelper::disposing() + { + mpSurface.reset(); + mpCairo.reset(); + mpVirtualDevice.disposeAndClear(); + mpDevice = nullptr; + mpSurfaceProvider = nullptr; + } + + void CanvasHelper::init( const ::basegfx::B2ISize& rSizePixel, + SurfaceProvider& rSurfaceProvider, + rendering::XGraphicDevice* pDevice ) + { + maSize = rSizePixel; + mpSurfaceProvider = &rSurfaceProvider; + mpDevice = pDevice; + } + + void CanvasHelper::setSize( const ::basegfx::B2ISize& rSize ) + { + maSize = rSize; + } + + void CanvasHelper::setSurface( const SurfaceSharedPtr& pSurface, bool bHasAlpha ) + { + mbHaveAlpha = bHasAlpha; + mpVirtualDevice.disposeAndClear(); + mpSurface = pSurface; + mpCairo = pSurface->getCairo(); + } + + static void setColor( cairo_t* pCairo, + const uno::Sequence<double>& rColor ) + { + if( rColor.getLength() > 3 ) + { + cairo_set_source_rgba( pCairo, + rColor[0], + rColor[1], + rColor[2], + rColor[3] ); + } + else if( rColor.getLength() == 3 ) + cairo_set_source_rgb( pCairo, + rColor[0], + rColor[1], + rColor[2] ); + } + + void CanvasHelper::useStates( const rendering::ViewState& viewState, + const rendering::RenderState& renderState, + bool bSetColor ) + { + cairo_matrix_t aViewMatrix; + cairo_matrix_t aRenderMatrix; + cairo_matrix_t aCombinedMatrix; + + cairo_matrix_init( &aViewMatrix, + viewState.AffineTransform.m00, viewState.AffineTransform.m10, viewState.AffineTransform.m01, + viewState.AffineTransform.m11, viewState.AffineTransform.m02, viewState.AffineTransform.m12); + cairo_matrix_init( &aRenderMatrix, + renderState.AffineTransform.m00, renderState.AffineTransform.m10, renderState.AffineTransform.m01, + renderState.AffineTransform.m11, renderState.AffineTransform.m02, renderState.AffineTransform.m12); + cairo_matrix_multiply( &aCombinedMatrix, &aRenderMatrix, &aViewMatrix); + + if( viewState.Clip.is() ) + { + SAL_INFO( "canvas.cairo", "view clip"); + + aViewMatrix.x0 = basegfx::fround( aViewMatrix.x0 ); + aViewMatrix.y0 = basegfx::fround( aViewMatrix.y0 ); + cairo_set_matrix( mpCairo.get(), &aViewMatrix ); + doPolyPolygonPath( viewState.Clip, Clip ); + } + + aCombinedMatrix.x0 = basegfx::fround( aCombinedMatrix.x0 ); + aCombinedMatrix.y0 = basegfx::fround( aCombinedMatrix.y0 ); + cairo_set_matrix( mpCairo.get(), &aCombinedMatrix ); + + if( renderState.Clip.is() ) + { + SAL_INFO( "canvas.cairo", "render clip BEGIN"); + + doPolyPolygonPath( renderState.Clip, Clip ); + SAL_INFO( "canvas.cairo", "render clip END"); + } + + if( bSetColor ) + setColor(mpCairo.get(),renderState.DeviceColor); + + cairo_operator_t compositingMode( CAIRO_OPERATOR_OVER ); + switch( renderState.CompositeOperation ) + { + case rendering::CompositeOperation::CLEAR: + compositingMode = CAIRO_OPERATOR_CLEAR; + break; + case rendering::CompositeOperation::SOURCE: + compositingMode = CAIRO_OPERATOR_SOURCE; + break; + case rendering::CompositeOperation::DESTINATION: + compositingMode = CAIRO_OPERATOR_DEST; + break; + case rendering::CompositeOperation::OVER: + compositingMode = CAIRO_OPERATOR_OVER; + break; + case rendering::CompositeOperation::UNDER: + compositingMode = CAIRO_OPERATOR_DEST; + break; + case rendering::CompositeOperation::INSIDE: + compositingMode = CAIRO_OPERATOR_IN; + break; + case rendering::CompositeOperation::INSIDE_REVERSE: + compositingMode = CAIRO_OPERATOR_OUT; + break; + case rendering::CompositeOperation::OUTSIDE: + compositingMode = CAIRO_OPERATOR_DEST_OVER; + break; + case rendering::CompositeOperation::OUTSIDE_REVERSE: + compositingMode = CAIRO_OPERATOR_DEST_OUT; + break; + case rendering::CompositeOperation::ATOP: + compositingMode = CAIRO_OPERATOR_ATOP; + break; + case rendering::CompositeOperation::ATOP_REVERSE: + compositingMode = CAIRO_OPERATOR_DEST_ATOP; + break; + case rendering::CompositeOperation::XOR: + compositingMode = CAIRO_OPERATOR_XOR; + break; + case rendering::CompositeOperation::ADD: + compositingMode = CAIRO_OPERATOR_ADD; + break; + case rendering::CompositeOperation::SATURATE: + compositingMode = CAIRO_OPERATOR_SATURATE; + break; + } + cairo_set_operator( mpCairo.get(), compositingMode ); + } + + void CanvasHelper::clear() + { + SAL_INFO( "canvas.cairo", "clear whole area: " << maSize.getWidth() << " x " << maSize.getHeight() ); + + if( !mpCairo ) + return; + + cairo_save( mpCairo.get() ); + + cairo_identity_matrix( mpCairo.get() ); + // this does not really differ from all-zero, as cairo + // internally converts to premultiplied alpha. but anyway, + // this keeps it consistent with the other canvas impls + if( mbHaveAlpha ) + cairo_set_source_rgba( mpCairo.get(), 1.0, 1.0, 1.0, 0.0 ); + else + cairo_set_source_rgb( mpCairo.get(), 1.0, 1.0, 1.0 ); + cairo_set_operator( mpCairo.get(), CAIRO_OPERATOR_SOURCE ); + + cairo_rectangle( mpCairo.get(), 0, 0, maSize.getWidth(), maSize.getHeight() ); + cairo_fill( mpCairo.get() ); + + cairo_restore( mpCairo.get() ); + } + + void CanvasHelper::drawLine( const rendering::XCanvas* /*pCanvas*/, + const geometry::RealPoint2D& aStartPoint, + const geometry::RealPoint2D& aEndPoint, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState ) + { + if( !mpCairo ) + return; + + cairo_save( mpCairo.get() ); + + cairo_set_line_width( mpCairo.get(), 1 ); + + useStates( viewState, renderState, true ); + + cairo_move_to( mpCairo.get(), aStartPoint.X + 0.5, aStartPoint.Y + 0.5 ); + cairo_line_to( mpCairo.get(), aEndPoint.X + 0.5, aEndPoint.Y + 0.5 ); + cairo_stroke( mpCairo.get() ); + + cairo_restore( mpCairo.get() ); + } + + void CanvasHelper::drawBezier( const rendering::XCanvas* , + const geometry::RealBezierSegment2D& aBezierSegment, + const geometry::RealPoint2D& aEndPoint, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState ) + { + if( !mpCairo ) + return; + + cairo_save( mpCairo.get() ); + + cairo_set_line_width( mpCairo.get(), 1 ); + + useStates( viewState, renderState, true ); + + cairo_move_to( mpCairo.get(), aBezierSegment.Px + 0.5, aBezierSegment.Py + 0.5 ); + // tdf#99165 correction of control points not needed here, only hairlines drawn + // (see cairo_set_line_width above) + cairo_curve_to( mpCairo.get(), + aBezierSegment.C1x + 0.5, aBezierSegment.C1y + 0.5, + aBezierSegment.C2x + 0.5, aBezierSegment.C2y + 0.5, + aEndPoint.X + 0.5, aEndPoint.Y + 0.5 ); + cairo_stroke( mpCairo.get() ); + + cairo_restore( mpCairo.get() ); + } + +constexpr OUStringLiteral PARAMETRICPOLYPOLYGON_IMPLEMENTATION_NAME = u"Canvas::ParametricPolyPolygon"; + + /** surfaceFromXBitmap Create a surface from XBitmap + * @param xBitmap bitmap image that will be used for the surface + * @param bHasAlpha will be set to true if resulting surface has alpha + * + * This is a helper function for the other surfaceFromXBitmap(). + * This function tries to create surface from xBitmap by checking if xBitmap is CanvasBitmap or SpriteCanvas. + * + * @return created surface or NULL + **/ + static SurfaceSharedPtr surfaceFromXBitmap( const uno::Reference< rendering::XBitmap >& xBitmap ) + { + CanvasBitmap* pBitmapImpl = dynamic_cast< CanvasBitmap* >( xBitmap.get() ); + if( pBitmapImpl ) + return pBitmapImpl->getSurface(); + + SurfaceProvider* pSurfaceProvider = dynamic_cast<SurfaceProvider*>( xBitmap.get() ); + if( pSurfaceProvider ) + return pSurfaceProvider->getSurface(); + + return SurfaceSharedPtr(); + } + + static ::BitmapEx bitmapExFromXBitmap( const uno::Reference< rendering::XBitmap >& xBitmap ) + { + // TODO(F1): Add support for floating point bitmap formats + uno::Reference<rendering::XIntegerReadOnlyBitmap> xIntBmp(xBitmap, + uno::UNO_QUERY_THROW); + ::BitmapEx aBmpEx = vcl::unotools::bitmapExFromXBitmap(xIntBmp); + if( !aBmpEx.IsEmpty() ) + return aBmpEx; + + // TODO(F1): extract pixel from XBitmap interface + ENSURE_OR_THROW( false, + "bitmapExFromXBitmap(): could not extract BitmapEx" ); + + return ::BitmapEx(); + } + + /** surfaceFromXBitmap Create a surface from XBitmap + * @param xBitmap bitmap image that will be used for the surface + * @param rDevice reference to the device into which we want to draw + * @param data will be filled with alpha data, if xBitmap is alpha/transparent image + * @param bHasAlpha will be set to true if resulting surface has alpha + * + * This function tries various methods for creating a surface from xBitmap. It also uses + * the helper function surfaceFromXBitmap( xBitmap, bHasAlpha ) + * + * @return created surface or NULL + **/ + static SurfaceSharedPtr surfaceFromXBitmap( const uno::Reference< rendering::XBitmap >& xBitmap, const SurfaceProviderRef& rSurfaceProvider, unsigned char*& data, bool& bHasAlpha ) + { + bHasAlpha = xBitmap->hasAlpha(); + SurfaceSharedPtr pSurface = surfaceFromXBitmap( xBitmap ); + if( pSurface ) + data = nullptr; + else + { + ::BitmapEx aBmpEx = bitmapExFromXBitmap(xBitmap); + ::Bitmap aBitmap = aBmpEx.GetBitmap(); + + // there's no pixmap for alpha bitmap. we might still + // use rgb pixmap and only access alpha pixels the + // slow way. now we just speedup rgb bitmaps + if( !aBmpEx.IsAlpha() ) + { + pSurface = rSurfaceProvider->createSurface( aBitmap ); + data = nullptr; + bHasAlpha = false; + } + + if( !pSurface ) + { + tools::Long nWidth; + tools::Long nHeight; + vcl::bitmap::CanvasCairoExtractBitmapData(aBmpEx, aBitmap, data, bHasAlpha, nWidth, nHeight); + + pSurface = rSurfaceProvider->getOutputDevice()->CreateSurface( + CairoSurfaceSharedPtr( + cairo_image_surface_create_for_data( + data, + bHasAlpha ? CAIRO_FORMAT_ARGB32 : CAIRO_FORMAT_RGB24, + nWidth, nHeight, nWidth*4 ), + &cairo_surface_destroy) ); + + SAL_INFO( "canvas.cairo","image: " << nWidth << " x " << nHeight << " alpha: " << bHasAlpha); + } + } + + return pSurface; + } + + static void addColorStops( cairo_pattern_t* pPattern, const uno::Sequence< uno::Sequence< double > >& rColors, const uno::Sequence< double >& rStops, bool bReverseStops ) + { + int i; + + OSL_ASSERT( rColors.getLength() == rStops.getLength() ); + + for( i = 0; i < rColors.getLength(); i++ ) + { + const uno::Sequence< double >& rColor( rColors[i] ); + float stop = bReverseStops ? 1 - rStops[i] : rStops[i]; + if( rColor.getLength() == 3 ) + cairo_pattern_add_color_stop_rgb( pPattern, stop, rColor[0], rColor[1], rColor[2] ); + else if( rColor.getLength() == 4 ) + { + double alpha = rColor[3]; + // cairo expects premultiplied alpha + cairo_pattern_add_color_stop_rgba( pPattern, stop, rColor[0]*alpha, rColor[1]*alpha, rColor[2]*alpha, alpha ); + } + } + } + + static uno::Sequence<double> lerp(const uno::Sequence<double>& rLeft, const uno::Sequence<double>& rRight, double fAlpha) + { + if( rLeft.getLength() == 3 ) + { + return + { + basegfx::utils::lerp(rLeft[0],rRight[0],fAlpha), + basegfx::utils::lerp(rLeft[1],rRight[1],fAlpha), + basegfx::utils::lerp(rLeft[2],rRight[2],fAlpha) + }; + } + else if( rLeft.getLength() == 4 ) + { + return + { + basegfx::utils::lerp(rLeft[0],rRight[0],fAlpha), + basegfx::utils::lerp(rLeft[1],rRight[1],fAlpha), + basegfx::utils::lerp(rLeft[2],rRight[2],fAlpha), + basegfx::utils::lerp(rLeft[3],rRight[3],fAlpha) + }; + } + + return {}; + } + + static cairo_pattern_t* patternFromParametricPolyPolygon( ::canvas::ParametricPolyPolygon const & rPolygon ) + { + cairo_pattern_t* pPattern = nullptr; + const ::canvas::ParametricPolyPolygon::Values aValues = rPolygon.getValues(); + double x0, x1, y0, y1, cx, cy, r0, r1; + + switch( aValues.meType ) + { + case ::canvas::ParametricPolyPolygon::GradientType::Linear: + x0 = 0; + y0 = 0; + x1 = 1; + y1 = 0; + pPattern = cairo_pattern_create_linear( x0, y0, x1, y1 ); + addColorStops( pPattern, aValues.maColors, aValues.maStops, false ); + break; + + case ::canvas::ParametricPolyPolygon::GradientType::Elliptical: + cx = 0; + cy = 0; + r0 = 0; + r1 = 1; + + pPattern = cairo_pattern_create_radial( cx, cy, r0, cy, cy, r1 ); + addColorStops( pPattern, aValues.maColors, aValues.maStops, true ); + break; + default: + break; + } + + return pPattern; + } + + static void doOperation( Operation aOperation, + cairo_t* pCairo, + const uno::Sequence< rendering::Texture >* pTextures, + const SurfaceProviderRef& pDevice, + const basegfx::B2DRange& rBounds ) + { + switch( aOperation ) + { + case Fill: + /* TODO: multitexturing */ + if( pTextures ) + { + const css::rendering::Texture& aTexture ( (*pTextures)[0] ); + if( aTexture.Bitmap.is() ) + { + unsigned char* data = nullptr; + bool bHasAlpha = false; + SurfaceSharedPtr pSurface = surfaceFromXBitmap( (*pTextures)[0].Bitmap, pDevice, data, bHasAlpha ); + + if( pSurface ) + { + cairo_pattern_t* pPattern; + + cairo_save( pCairo ); + + css::geometry::AffineMatrix2D aTransform( aTexture.AffineTransform ); + cairo_matrix_t aScaleMatrix, aTextureMatrix, aScaledTextureMatrix; + + cairo_matrix_init( &aTextureMatrix, + aTransform.m00, aTransform.m10, aTransform.m01, + aTransform.m11, aTransform.m02, aTransform.m12); + + geometry::IntegerSize2D aSize = aTexture.Bitmap->getSize(); + + cairo_matrix_init_scale( &aScaleMatrix, 1.0/aSize.Width, 1.0/aSize.Height ); + cairo_matrix_multiply( &aScaledTextureMatrix, &aTextureMatrix, &aScaleMatrix ); + cairo_matrix_invert( &aScaledTextureMatrix ); + + // we don't care about repeat mode yet, so the workaround is disabled for now + pPattern = cairo_pattern_create_for_surface( pSurface->getCairoSurface().get() ); + + if( aTexture.RepeatModeX == rendering::TexturingMode::REPEAT && + aTexture.RepeatModeY == rendering::TexturingMode::REPEAT ) + { + cairo_pattern_set_extend( pPattern, CAIRO_EXTEND_REPEAT ); + } + else if ( aTexture.RepeatModeX == rendering::TexturingMode::NONE && + aTexture.RepeatModeY == rendering::TexturingMode::NONE ) + { + cairo_pattern_set_extend( pPattern, CAIRO_EXTEND_NONE ); + } + else if ( aTexture.RepeatModeX == rendering::TexturingMode::CLAMP && + aTexture.RepeatModeY == rendering::TexturingMode::CLAMP ) + { + cairo_pattern_set_extend( pPattern, CAIRO_EXTEND_PAD ); + } + + aScaledTextureMatrix.x0 = basegfx::fround( aScaledTextureMatrix.x0 ); + aScaledTextureMatrix.y0 = basegfx::fround( aScaledTextureMatrix.y0 ); + + double x1, y1, x2, y2; + cairo_path_extents(pCairo, &x1, &y1, &x2, &y2); + aScaledTextureMatrix.x0 -= (x1 * aScaledTextureMatrix.xx); + aScaledTextureMatrix.y0 -= (y1 * aScaledTextureMatrix.yy); + + cairo_pattern_set_matrix( pPattern, &aScaledTextureMatrix ); + + cairo_set_source( pCairo, pPattern ); + if( !bHasAlpha ) + cairo_set_operator( pCairo, CAIRO_OPERATOR_SOURCE ); + cairo_fill( pCairo ); + + cairo_restore( pCairo ); + + cairo_pattern_destroy( pPattern ); + } + + if( data ) + free( data ); + } + else if( aTexture.Gradient.is() ) + { + uno::Reference< lang::XServiceInfo > xRef( aTexture.Gradient, uno::UNO_QUERY ); + + SAL_INFO( "canvas.cairo", "gradient fill" ); + if( xRef.is() && xRef->getImplementationName() == PARAMETRICPOLYPOLYGON_IMPLEMENTATION_NAME ) + { + // TODO(Q1): Maybe use dynamic_cast here + + // TODO(E1): Return value + // TODO(F1): FillRule + SAL_INFO( "canvas.cairo", "known implementation" ); + + ::canvas::ParametricPolyPolygon* pPolyImpl = static_cast< ::canvas::ParametricPolyPolygon* >( aTexture.Gradient.get() ); + css::geometry::AffineMatrix2D aTransform( aTexture.AffineTransform ); + cairo_matrix_t aTextureMatrix; + + cairo_matrix_init( &aTextureMatrix, + aTransform.m00, aTransform.m10, aTransform.m01, + aTransform.m11, aTransform.m02, aTransform.m12); + if( pPolyImpl->getValues().meType == canvas::ParametricPolyPolygon::GradientType::Rectangular ) + { + // no general path gradient yet in cairo; emulate then + cairo_save( pCairo ); + cairo_clip( pCairo ); + + // fill bound rect with start color + cairo_rectangle( pCairo, rBounds.getMinX(), rBounds.getMinY(), + rBounds.getWidth(), rBounds.getHeight() ); + setColor(pCairo,pPolyImpl->getValues().maColors[0]); + cairo_fill(pCairo); + + cairo_transform( pCairo, &aTextureMatrix ); + + // longest line in gradient bound rect + const unsigned int nGradientSize( + static_cast<unsigned int>( + ::basegfx::B2DVector(rBounds.getMinimum() - rBounds.getMaximum()).getLength() + 1.0 ) ); + + // typical number for pixel of the same color (strip size) + const unsigned int nStripSize( nGradientSize < 50 ? 2 : 4 ); + + // use at least three steps, and at utmost the number of color + // steps + const unsigned int nStepCount( + std::max( + 3U, + std::min( + nGradientSize / nStripSize, + 128U )) + 1 ); + + const uno::Sequence<double>* pColors=&pPolyImpl->getValues().maColors[0]; + basegfx::utils::KeyStopLerp aLerper(pPolyImpl->getValues().maStops); + for( unsigned int i=1; i<nStepCount; ++i ) + { + const double fT( i/double(nStepCount) ); + + std::ptrdiff_t nIndex; + double fAlpha; + std::tie(nIndex,fAlpha)=aLerper.lerp(fT); + + setColor(pCairo, lerp(pColors[nIndex], pColors[nIndex+1], fAlpha)); + cairo_rectangle( pCairo, -1+fT, -1+fT, 2-2*fT, 2-2*fT ); + cairo_fill(pCairo); + } + + cairo_restore( pCairo ); + } + else + { + cairo_pattern_t* pPattern = patternFromParametricPolyPolygon( *pPolyImpl ); + + if( pPattern ) + { + SAL_INFO( "canvas.cairo", "filling with pattern" ); + + cairo_save( pCairo ); + + cairo_transform( pCairo, &aTextureMatrix ); + cairo_set_source( pCairo, pPattern ); + cairo_fill( pCairo ); + cairo_restore( pCairo ); + + cairo_pattern_destroy( pPattern ); + } + } + } + } + } + else + cairo_fill( pCairo ); + SAL_INFO( "canvas.cairo", "fill"); + break; + case Stroke: + cairo_stroke( pCairo ); + SAL_INFO( "canvas.cairo", "stroke"); + break; + case Clip: + cairo_clip( pCairo ); + SAL_INFO( "canvas.cairo", "clip"); + break; + } + } + + static void clipNULL( cairo_t *pCairo ) + { + SAL_INFO( "canvas.cairo", "clipNULL"); + cairo_matrix_t aOrigMatrix, aIdentityMatrix; + + /* we set identity matrix here to overcome bug in cairo 0.9.2 + where XCreatePixmap is called with zero width and height. + + it also reaches faster path in cairo clipping code. + */ + cairo_matrix_init_identity( &aIdentityMatrix ); + cairo_get_matrix( pCairo, &aOrigMatrix ); + cairo_set_matrix( pCairo, &aIdentityMatrix ); + + cairo_reset_clip( pCairo ); + cairo_rectangle( pCairo, 0, 0, 1, 1 ); + cairo_clip( pCairo ); + cairo_rectangle( pCairo, 2, 0, 1, 1 ); + cairo_clip( pCairo ); + + /* restore the original matrix */ + cairo_set_matrix( pCairo, &aOrigMatrix ); + } + + void doPolyPolygonImplementation( const ::basegfx::B2DPolyPolygon& aPolyPolygon, + Operation aOperation, + cairo_t* pCairo, + const uno::Sequence< rendering::Texture >* pTextures, + const SurfaceProviderRef& pDevice, + rendering::FillRule eFillrule ) + { + if( pTextures ) + ENSURE_ARG_OR_THROW( pTextures->hasElements(), + "CanvasHelper::fillTexturedPolyPolygon: empty texture sequence"); + + bool bOpToDo = false; + cairo_matrix_t aOrigMatrix, aIdentityMatrix; + double nX, nY, nBX, nBY, nAX, nAY, nLastX(0.0), nLastY(0.0); + + cairo_get_matrix( pCairo, &aOrigMatrix ); + cairo_matrix_init_identity( &aIdentityMatrix ); + cairo_set_matrix( pCairo, &aIdentityMatrix ); + + cairo_set_fill_rule( pCairo, + eFillrule == rendering::FillRule_EVEN_ODD ? + CAIRO_FILL_RULE_EVEN_ODD : CAIRO_FILL_RULE_WINDING ); + + for( sal_uInt32 nPolygonIndex = 0; nPolygonIndex < aPolyPolygon.count(); nPolygonIndex++ ) + { + const ::basegfx::B2DPolygon& aPolygon( aPolyPolygon.getB2DPolygon( nPolygonIndex ) ); + const sal_uInt32 nPointCount( aPolygon.count() ); + // to correctly render closed curves, need to output first + // point twice (so output one additional point) + const sal_uInt32 nExtendedPointCount( nPointCount + + int(aPolygon.isClosed() && aPolygon.areControlPointsUsed()) ); + + if( nPointCount > 1) + { + bool bIsBezier = aPolygon.areControlPointsUsed(); + ::basegfx::B2DPoint aA, aB, aP; + + for( sal_uInt32 j=0; j < nExtendedPointCount; j++ ) + { + aP = aPolygon.getB2DPoint( j % nPointCount ); + + nX = aP.getX(); + nY = aP.getY(); + cairo_matrix_transform_point( &aOrigMatrix, &nX, &nY ); + + if (!bIsBezier && aOperation == Clip) + { + nX = basegfx::fround( nX ); + nY = basegfx::fround( nY ); + } + + if( aOperation == Stroke ) + { + nX += 0.5; + nY += 0.5; + } + + if( j==0 ) + { + cairo_move_to( pCairo, nX, nY ); + SAL_INFO( "canvas.cairo", "move to " << nX << "," << nY ); + } + else + { + if( bIsBezier ) + { + aA = aPolygon.getNextControlPoint( (j-1) % nPointCount ); + aB = aPolygon.getPrevControlPoint( j % nPointCount ); + + nAX = aA.getX(); + nAY = aA.getY(); + nBX = aB.getX(); + nBY = aB.getY(); + + cairo_matrix_transform_point( &aOrigMatrix, &nAX, &nAY ); + cairo_matrix_transform_point( &aOrigMatrix, &nBX, &nBY ); + + if( aOperation == Stroke ) + { + nAX += 0.5; + nAY += 0.5; + nBX += 0.5; + nBY += 0.5; + } + + // tdf#99165 if the control points are 'empty', create the mathematical + // correct replacement ones to avoid problems with the graphical sub-system + // tdf#101026 The 1st attempt to create a mathematically correct replacement control + // vector was wrong. Best alternative is one as close as possible which means short. + if (basegfx::fTools::equal(nAX, nLastX) && basegfx::fTools::equal(nAY, nLastY)) + { + nAX = nLastX + ((nBX - nLastX) * 0.0005); + nAY = nLastY + ((nBY - nLastY) * 0.0005); + } + + if(basegfx::fTools::equal(nBX, nX) && basegfx::fTools::equal(nBY, nY)) + { + nBX = nX + ((nAX - nX) * 0.0005); + nBY = nY + ((nAY - nY) * 0.0005); + } + + cairo_curve_to( pCairo, nAX, nAY, nBX, nBY, nX, nY ); + } + else + { + cairo_line_to( pCairo, nX, nY ); + SAL_INFO( "canvas.cairo", "line to " << nX << "," << nY ); + } + bOpToDo = true; + } + + nLastX = nX; + nLastY = nY; + } + + if( aPolygon.isClosed() ) + cairo_close_path( pCairo ); + + } + else + { + SAL_INFO( "canvas.cairo", "empty polygon for op: " << aOperation ); + if( aOperation == Clip ) + { + clipNULL( pCairo ); + + return; + } + } + } + + if( aOperation == Fill && pTextures ) + { + cairo_set_matrix( pCairo, &aOrigMatrix ); + doOperation( aOperation, pCairo, pTextures, pDevice, aPolyPolygon.getB2DRange() ); + cairo_set_matrix( pCairo, &aIdentityMatrix ); + } + + if( bOpToDo && ( aOperation != Fill || !pTextures ) ) + doOperation( aOperation, pCairo, pTextures, pDevice, aPolyPolygon.getB2DRange() ); + + cairo_set_matrix( pCairo, &aOrigMatrix ); + + if( aPolyPolygon.count() == 0 && aOperation == Clip ) + clipNULL( pCairo ); + } + + void CanvasHelper::doPolyPolygonPath( const uno::Reference< rendering::XPolyPolygon2D >& xPolyPolygon, + Operation aOperation, + bool bNoLineJoin, + const uno::Sequence< rendering::Texture >* pTextures ) const + { + const ::basegfx::B2DPolyPolygon& rPolyPoly( + ::basegfx::unotools::b2DPolyPolygonFromXPolyPolygon2D(xPolyPolygon) ); + + cairo_t* pCairo = mpCairo.get(); + + if(bNoLineJoin && aOperation == Stroke) + { + // emulate rendering::PathJoinType::NONE by painting single edges + for(sal_uInt32 a(0); a < rPolyPoly.count(); a++) + { + const basegfx::B2DPolygon& aCandidate(rPolyPoly.getB2DPolygon(a)); + const sal_uInt32 nPointCount(aCandidate.count()); + + if(nPointCount) + { + const sal_uInt32 nEdgeCount(aCandidate.isClosed() ? nPointCount: nPointCount - 1); + basegfx::B2DPolygon aEdge; + aEdge.append(aCandidate.getB2DPoint(0)); + aEdge.append(basegfx::B2DPoint(0.0, 0.0)); + + for(sal_uInt32 b(0); b < nEdgeCount; b++) + { + const sal_uInt32 nNextIndex((b + 1) % nPointCount); + aEdge.setB2DPoint(1, aCandidate.getB2DPoint(nNextIndex)); + aEdge.setNextControlPoint(0, aCandidate.getNextControlPoint(b % nPointCount)); + aEdge.setPrevControlPoint(1, aCandidate.getPrevControlPoint(nNextIndex)); + + doPolyPolygonImplementation( basegfx::B2DPolyPolygon(aEdge), + aOperation, + pCairo, pTextures, + mpSurfaceProvider, + xPolyPolygon->getFillRule() ); + + // prepare next step + aEdge.setB2DPoint(0, aEdge.getB2DPoint(1)); + } + } + } + } + else + { + doPolyPolygonImplementation( rPolyPoly, aOperation, + pCairo, pTextures, + mpSurfaceProvider, + xPolyPolygon->getFillRule() ); + } + } + + uno::Reference< rendering::XCachedPrimitive > CanvasHelper::drawPolyPolygon( const rendering::XCanvas* , + const uno::Reference< rendering::XPolyPolygon2D >& xPolyPolygon, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState ) + { +#ifdef CAIRO_CANVAS_PERF_TRACE + struct timespec aTimer; + mxDevice->startPerfTrace( &aTimer ); +#endif + + if( mpCairo ) + { + cairo_save( mpCairo.get() ); + + cairo_set_line_width( mpCairo.get(), 1 ); + + useStates( viewState, renderState, true ); + doPolyPolygonPath( xPolyPolygon, Stroke ); + + cairo_restore( mpCairo.get() ); + } + else + SAL_INFO( "canvas.cairo", "CanvasHelper called after it was disposed"); + +#ifdef CAIRO_CANVAS_PERF_TRACE + mxDevice->stopPerfTrace( &aTimer, "drawPolyPolygon" ); +#endif + + return uno::Reference< rendering::XCachedPrimitive >(nullptr); + } + + uno::Reference< rendering::XCachedPrimitive > CanvasHelper::strokePolyPolygon( const rendering::XCanvas* , + const uno::Reference< rendering::XPolyPolygon2D >& xPolyPolygon, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState, + const rendering::StrokeAttributes& strokeAttributes ) + { +#ifdef CAIRO_CANVAS_PERF_TRACE + struct timespec aTimer; + mxDevice->startPerfTrace( &aTimer ); +#endif + + if( mpCairo ) + { + cairo_save( mpCairo.get() ); + + useStates( viewState, renderState, true ); + + cairo_matrix_t aMatrix; + cairo_get_matrix( mpCairo.get(), &aMatrix ); + double scaleFactorX = 1; + double scaleFactorY = 0; + cairo_matrix_transform_distance( &aMatrix, &scaleFactorX, &scaleFactorY ); + double scaleFactor = basegfx::B2DVector( scaleFactorX, scaleFactorY ).getLength(); + cairo_set_line_width( mpCairo.get(), strokeAttributes.StrokeWidth * scaleFactor ); + + cairo_set_miter_limit( mpCairo.get(), strokeAttributes.MiterLimit ); + + // FIXME: cairo doesn't handle end cap so far (rodo) + switch( strokeAttributes.StartCapType ) + { + case rendering::PathCapType::BUTT: + cairo_set_line_cap( mpCairo.get(), CAIRO_LINE_CAP_BUTT ); + break; + case rendering::PathCapType::ROUND: + cairo_set_line_cap( mpCairo.get(), CAIRO_LINE_CAP_ROUND ); + break; + case rendering::PathCapType::SQUARE: + cairo_set_line_cap( mpCairo.get(), CAIRO_LINE_CAP_SQUARE ); + break; + } + + bool bNoLineJoin(false); + + switch( strokeAttributes.JoinType ) + { + case rendering::PathJoinType::NONE: + bNoLineJoin = true; + [[fallthrough]]; // cairo doesn't have join type NONE so we use MITER as it's pretty close + case rendering::PathJoinType::MITER: + cairo_set_line_join( mpCairo.get(), CAIRO_LINE_JOIN_MITER ); + break; + case rendering::PathJoinType::ROUND: + cairo_set_line_join( mpCairo.get(), CAIRO_LINE_JOIN_ROUND ); + break; + case rendering::PathJoinType::BEVEL: + cairo_set_line_join( mpCairo.get(), CAIRO_LINE_JOIN_BEVEL ); + break; + } + + //tdf#103026 If the scaling is 0, then all dashes become zero so + //cairo will set the cairo_t status to CAIRO_STATUS_INVALID_DASH + //and no further drawing will occur + if (strokeAttributes.DashArray.hasElements() && scaleFactor > 0.0) + { + auto aDashArray(comphelper::sequenceToContainer<std::vector<double>>(strokeAttributes.DashArray)); + for (auto& rDash : aDashArray) + rDash *= scaleFactor; + cairo_set_dash(mpCairo.get(), aDashArray.data(), aDashArray.size(), 0); + } + + // TODO(rodo) use LineArray of strokeAttributes + + doPolyPolygonPath( xPolyPolygon, Stroke, bNoLineJoin ); + + cairo_restore( mpCairo.get() ); + } + else + SAL_INFO( "canvas.cairo", "CanvasHelper called after it was disposed"); + +#ifdef CAIRO_CANVAS_PERF_TRACE + mxDevice->stopPerfTrace( &aTimer, "strokePolyPolygon" ); +#endif + + // TODO(P1): Provide caching here. + return uno::Reference< rendering::XCachedPrimitive >(nullptr); + } + + uno::Reference< rendering::XCachedPrimitive > CanvasHelper::strokeTexturedPolyPolygon( const rendering::XCanvas* , + const uno::Reference< rendering::XPolyPolygon2D >& /*xPolyPolygon*/, + const rendering::ViewState& /*viewState*/, + const rendering::RenderState& /*renderState*/, + const uno::Sequence< rendering::Texture >& /*textures*/, + const rendering::StrokeAttributes& /*strokeAttributes*/ ) + { + // TODO + return uno::Reference< rendering::XCachedPrimitive >(nullptr); + } + + uno::Reference< rendering::XCachedPrimitive > CanvasHelper::strokeTextureMappedPolyPolygon( const rendering::XCanvas* , + const uno::Reference< rendering::XPolyPolygon2D >& /*xPolyPolygon*/, + const rendering::ViewState& /*viewState*/, + const rendering::RenderState& /*renderState*/, + const uno::Sequence< rendering::Texture >& /*textures*/, + const uno::Reference< geometry::XMapping2D >& /*xMapping*/, + const rendering::StrokeAttributes& /*strokeAttributes*/ ) + { + // TODO + return uno::Reference< rendering::XCachedPrimitive >(nullptr); + } + + uno::Reference< rendering::XPolyPolygon2D > CanvasHelper::queryStrokeShapes( const rendering::XCanvas* , + const uno::Reference< rendering::XPolyPolygon2D >& /*xPolyPolygon*/, + const rendering::ViewState& /*viewState*/, + const rendering::RenderState& /*renderState*/, + const rendering::StrokeAttributes& /*strokeAttributes*/ ) + { + // TODO + return uno::Reference< rendering::XPolyPolygon2D >(nullptr); + } + + uno::Reference< rendering::XCachedPrimitive > CanvasHelper::fillPolyPolygon( const rendering::XCanvas* , + const uno::Reference< rendering::XPolyPolygon2D >& xPolyPolygon, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState ) + { +#ifdef CAIRO_CANVAS_PERF_TRACE + struct timespec aTimer; + mxDevice->startPerfTrace( &aTimer ); +#endif + + if( mpCairo ) + { + cairo_save( mpCairo.get() ); + + useStates( viewState, renderState, true ); + doPolyPolygonPath( xPolyPolygon, Fill ); + + cairo_restore( mpCairo.get() ); + } + else + SAL_INFO( "canvas.cairo", "CanvasHelper called after it was disposed"); + +#ifdef CAIRO_CANVAS_PERF_TRACE + mxDevice->stopPerfTrace( &aTimer, "fillPolyPolygon" ); +#endif + + return uno::Reference< rendering::XCachedPrimitive >(nullptr); + } + + uno::Reference< rendering::XCachedPrimitive > CanvasHelper::fillTexturedPolyPolygon( const rendering::XCanvas* , + const uno::Reference< rendering::XPolyPolygon2D >& xPolyPolygon, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState, + const uno::Sequence< rendering::Texture >& textures ) + { + if( mpCairo ) + { + cairo_save( mpCairo.get() ); + + useStates( viewState, renderState, true ); + doPolyPolygonPath( xPolyPolygon, Fill, false, &textures ); + + cairo_restore( mpCairo.get() ); + } + + return uno::Reference< rendering::XCachedPrimitive >(nullptr); + } + + uno::Reference< rendering::XCachedPrimitive > CanvasHelper::fillTextureMappedPolyPolygon( const rendering::XCanvas* , + const uno::Reference< rendering::XPolyPolygon2D >& /*xPolyPolygon*/, + const rendering::ViewState& /*viewState*/, + const rendering::RenderState& /*renderState*/, + const uno::Sequence< rendering::Texture >& /*textures*/, + const uno::Reference< geometry::XMapping2D >& /*xMapping*/ ) + { + // TODO + return uno::Reference< rendering::XCachedPrimitive >(nullptr); + } + + uno::Reference< rendering::XCachedPrimitive > CanvasHelper::implDrawBitmapSurface( const rendering::XCanvas* pCanvas, + const SurfaceSharedPtr& pInputSurface, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState, + const geometry::IntegerSize2D& rSize, + bool bModulateColors, + bool bHasAlpha ) + { + SurfaceSharedPtr pSurface=pInputSurface; + uno::Reference< rendering::XCachedPrimitive > rv; + geometry::IntegerSize2D aBitmapSize = rSize; + + if( mpCairo ) + { + cairo_save( mpCairo.get() ); + + cairo_rectangle( mpCairo.get(), 0, 0, maSize.getWidth(), maSize.getHeight() ); + cairo_clip( mpCairo.get() ); + + useStates( viewState, renderState, true ); + + cairo_matrix_t aMatrix; + + cairo_get_matrix( mpCairo.get(), &aMatrix ); + if( ! ::rtl::math::approxEqual( aMatrix.xx, 1 ) && + ! ::rtl::math::approxEqual( aMatrix.yy, 1 ) && + ::rtl::math::approxEqual( aMatrix.x0, 0 ) && + ::rtl::math::approxEqual( aMatrix.y0, 0 ) && + basegfx::fround( rSize.Width * aMatrix.xx ) > 8 && + basegfx::fround( rSize.Height* aMatrix.yy ) > 8 ) + { + double dWidth, dHeight; + + dWidth = basegfx::fround( rSize.Width * aMatrix.xx ); + dHeight = basegfx::fround( rSize.Height* aMatrix.yy ); + aBitmapSize.Width = static_cast<sal_Int32>( dWidth ); + aBitmapSize.Height = static_cast<sal_Int32>( dHeight ); + + SurfaceSharedPtr pScaledSurface = mpSurfaceProvider->createSurface( + ::basegfx::B2ISize( aBitmapSize.Width, aBitmapSize.Height ), + bHasAlpha ? CAIRO_CONTENT_COLOR_ALPHA : CAIRO_CONTENT_COLOR ); + CairoSharedPtr pCairo = pScaledSurface->getCairo(); + + cairo_set_operator( pCairo.get(), CAIRO_OPERATOR_SOURCE ); + // add 0.5px to size to avoid rounding errors in cairo, leading sometimes to random data on the image right/bottom borders + cairo_scale( pCairo.get(), (dWidth+0.5)/rSize.Width, (dHeight+0.5)/rSize.Height ); + cairo_set_source_surface( pCairo.get(), pSurface->getCairoSurface().get(), 0, 0 ); + cairo_paint( pCairo.get() ); + + pSurface = pScaledSurface; + + aMatrix.xx = aMatrix.yy = 1; + cairo_set_matrix( mpCairo.get(), &aMatrix ); + + rv.set( + new CachedBitmap( pSurface, viewState, renderState, + // cast away const, need to + // change refcount (as this is + // ~invisible to client code, + // still logically const) + const_cast< rendering::XCanvas* >(pCanvas)) ); + } + + if( !bHasAlpha && mbHaveAlpha ) + { + double x, y, width, height; + + x = y = 0; + width = aBitmapSize.Width; + height = aBitmapSize.Height; + cairo_matrix_transform_point( &aMatrix, &x, &y ); + cairo_matrix_transform_distance( &aMatrix, &width, &height ); + + // in case the bitmap doesn't have alpha and covers whole area + // we try to change surface to plain rgb + SAL_INFO( "canvas.cairo","chance to change surface to rgb, " << x << ", " << y << ", " << width << " x " << height << " (" << maSize.getWidth() << " x " << maSize.getHeight() << ")" ); + if( x <= 0 && y <= 0 && x + width >= maSize.getWidth() && y + height >= maSize.getHeight() ) + { + SAL_INFO( "canvas.cairo","trying to change surface to rgb"); + if( mpSurfaceProvider ) { + SurfaceSharedPtr pNewSurface = mpSurfaceProvider->changeSurface(); + + if( pNewSurface ) + setSurface( pNewSurface, false ); + + // set state to new mpCairo.get() + useStates( viewState, renderState, true ); + // use the possibly modified matrix + cairo_set_matrix( mpCairo.get(), &aMatrix ); + } + } + } + + cairo_set_source_surface( mpCairo.get(), pSurface->getCairoSurface().get(), 0, 0 ); + if( !bHasAlpha && + ::rtl::math::approxEqual( aMatrix.xx, 1 ) && + ::rtl::math::approxEqual( aMatrix.yy, 1 ) && + ::rtl::math::approxEqual( aMatrix.x0, 0 ) && + ::rtl::math::approxEqual( aMatrix.y0, 0 ) ) + cairo_set_operator( mpCairo.get(), CAIRO_OPERATOR_SOURCE ); + cairo_pattern_set_extend( cairo_get_source(mpCairo.get()), CAIRO_EXTEND_PAD ); + cairo_rectangle( mpCairo.get(), 0, 0, aBitmapSize.Width, aBitmapSize.Height ); + cairo_clip( mpCairo.get() ); + + // Use cairo_matrix_transform_distance() to determine the scaling, as that works even if + // the matrix also has rotation. + double fPixelWidth = rSize.Width; + double fPixelHeight = rSize.Height; + cairo_matrix_transform_distance(&aMatrix, &fPixelWidth, &fPixelHeight); + int nPixelWidth = std::round(fPixelWidth); + int nPixelHeight = std::round(fPixelHeight); + if (std::abs(nPixelWidth) > 0 && std::abs(nPixelHeight) > 0) + { + // Only render the image if it's at least 1x1 px sized. + if (bModulateColors) + cairo_paint_with_alpha(mpCairo.get(), renderState.DeviceColor[3]); + else + { + cairo_paint(mpCairo.get()); + if (cairo_status(mpCairo.get()) != CAIRO_STATUS_SUCCESS) + { + SAL_WARN("canvas.cairo", "cairo_paint() failed: " << cairo_status_to_string( + cairo_status(mpCairo.get()))); + } + } + } + cairo_restore( mpCairo.get() ); + } + else + SAL_INFO( "canvas.cairo", "CanvasHelper called after it was disposed"); + + return rv; // uno::Reference< rendering::XCachedPrimitive >(NULL); + } + + uno::Reference< rendering::XCachedPrimitive > CanvasHelper::drawBitmap( const rendering::XCanvas* pCanvas, + const uno::Reference< rendering::XBitmap >& xBitmap, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState ) + { +#ifdef CAIRO_CANVAS_PERF_TRACE + struct timespec aTimer; + mxDevice->startPerfTrace( &aTimer ); +#endif + + uno::Reference< rendering::XCachedPrimitive > rv; + unsigned char* data = nullptr; + bool bHasAlpha = false; + SurfaceSharedPtr pSurface = surfaceFromXBitmap( xBitmap, mpSurfaceProvider, data, bHasAlpha ); + geometry::IntegerSize2D aSize = xBitmap->getSize(); + + if( pSurface ) + { + rv = implDrawBitmapSurface( pCanvas, pSurface, viewState, renderState, aSize, false, bHasAlpha ); + + if( data ) + free( data ); + } + else + rv.clear(); + +#ifdef CAIRO_CANVAS_PERF_TRACE + mxDevice->stopPerfTrace( &aTimer, "drawBitmap" ); +#endif + + return rv; + } + + uno::Reference< rendering::XCachedPrimitive > CanvasHelper::drawBitmapModulated( const rendering::XCanvas* pCanvas, + const uno::Reference< rendering::XBitmap >& xBitmap, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState ) + { +#ifdef CAIRO_CANVAS_PERF_TRACE + struct timespec aTimer; + mxDevice->startPerfTrace( &aTimer ); +#endif + + uno::Reference< rendering::XCachedPrimitive > rv; + unsigned char* data = nullptr; + bool bHasAlpha = false; + SurfaceSharedPtr pSurface = surfaceFromXBitmap( xBitmap, mpSurfaceProvider, data, bHasAlpha ); + geometry::IntegerSize2D aSize = xBitmap->getSize(); + + if( pSurface ) + { + rv = implDrawBitmapSurface( pCanvas, pSurface, viewState, renderState, aSize, true, bHasAlpha ); + + if( data ) + free( data ); + } + else + rv.clear(); + +#ifdef CAIRO_CANVAS_PERF_TRACE + mxDevice->stopPerfTrace( &aTimer, "drawBitmap" ); +#endif + + return rv; + } + + + geometry::IntegerSize2D CanvasHelper::getSize() const + { + if( !mpSurfaceProvider ) + return geometry::IntegerSize2D(1, 1); // we're disposed + + return ::basegfx::unotools::integerSize2DFromB2ISize( maSize ); + } + + uno::Reference< rendering::XBitmap > CanvasHelper::getScaledBitmap( const geometry::RealSize2D& newSize, + bool /*beFast*/ ) + { +#ifdef CAIRO_CANVAS_PERF_TRACE + struct timespec aTimer; + mxDevice->startPerfTrace( &aTimer ); +#endif + + if( mpCairo ) + { + return uno::Reference< rendering::XBitmap >( new CanvasBitmap( ::basegfx::B2ISize( ::canvas::tools::roundUp( newSize.Width ), + ::canvas::tools::roundUp( newSize.Height ) ), + mpSurfaceProvider, mpDevice, false ) ); + } + else + SAL_INFO( "canvas.cairo", "CanvasHelper called after it was disposed"); + +#ifdef CAIRO_CANVAS_PERF_TRACE + mxDevice->stopPerfTrace( &aTimer, "getScaledBitmap" ); +#endif + + return uno::Reference< rendering::XBitmap >(); + } + + uno::Sequence< sal_Int8 > CanvasHelper::getData( rendering::IntegerBitmapLayout& aLayout, + const geometry::IntegerRectangle2D& rect ) + { + if( mpCairo ) + { + const sal_Int32 nWidth( rect.X2 - rect.X1 ); + const sal_Int32 nHeight( rect.Y2 - rect.Y1 ); + const cairo_format_t eFormat( mbHaveAlpha ? CAIRO_FORMAT_ARGB32 : CAIRO_FORMAT_RGB24 ); + uno::Sequence< sal_Int8 > aRes( 4*nWidth*nHeight ); + sal_Int8* pData = aRes.getArray(); + cairo_surface_t* pImageSurface = cairo_image_surface_create_for_data( reinterpret_cast<unsigned char *>(pData), + eFormat, + nWidth, nHeight, 4*nWidth ); + cairo_t* pCairo = cairo_create( pImageSurface ); + cairo_set_source_surface( pCairo, mpSurface->getCairoSurface().get(), -rect.X1, -rect.Y1); + cairo_paint( pCairo ); + cairo_destroy( pCairo ); + cairo_surface_destroy( pImageSurface ); + + aLayout = impl_getMemoryLayout( nWidth, nHeight ); + + return aRes; + } + + return uno::Sequence< sal_Int8 >(); + } + + uno::Sequence< sal_Int8 > CanvasHelper::getPixel( rendering::IntegerBitmapLayout& /*bitmapLayout*/, + const geometry::IntegerPoint2D& /*pos*/ ) + { + return uno::Sequence< sal_Int8 >(); + } + + namespace + { + class CairoColorSpace : 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<rendering::ARGBColor> 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<rendering::XColorSpace*>(this), 0); + + uno::Sequence< rendering::RGBColor > aRes(nLen/4); + rendering::RGBColor* pOut( aRes.getArray() ); + for( std::size_t i=0; i<nLen; i+=4 ) + { + const double fAlpha(pIn[3]); + if( fAlpha == 0.0 ) + *pOut++ = rendering::RGBColor(0.0, 0.0, 0.0); + else + *pOut++ = rendering::RGBColor(pIn[2]/fAlpha,pIn[1]/fAlpha,pIn[0]/fAlpha); + pIn += 4; + } + return aRes; + } + virtual uno::Sequence< rendering::ARGBColor > 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<rendering::XColorSpace*>(this), 0); + + uno::Sequence< rendering::ARGBColor > aRes(nLen/4); + rendering::ARGBColor* pOut( aRes.getArray() ); + for( std::size_t i=0; i<nLen; i+=4 ) + { + const double fAlpha(pIn[3]); + if( fAlpha == 0.0 ) + *pOut++ = rendering::ARGBColor(0.0, 0.0, 0.0, 0.0); + else + *pOut++ = rendering::ARGBColor(fAlpha,pIn[2]/fAlpha,pIn[1]/fAlpha,pIn[0]/fAlpha); + pIn += 4; + } + return aRes; + } + virtual uno::Sequence< rendering::ARGBColor > 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<rendering::XColorSpace*>(this), 0); + + uno::Sequence< rendering::ARGBColor > aRes(nLen/4); + rendering::ARGBColor* pOut( aRes.getArray() ); + for( std::size_t i=0; i<nLen; i+=4 ) + { + *pOut++ = rendering::ARGBColor(pIn[3],pIn[2],pIn[1],pIn[1]); + pIn += 4; + } + return aRes; + } + virtual uno::Sequence< double > 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; i<nLen; ++i ) + { + *pColors++ = pIn->Blue; + *pColors++ = pIn->Green; + *pColors++ = pIn->Red; + *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; i<nLen; ++i ) + { + *pColors++ = pIn->Alpha*pIn->Blue; + *pColors++ = pIn->Alpha*pIn->Green; + *pColors++ = pIn->Alpha*pIn->Red; + *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; i<nLen; ++i ) + { + *pColors++ = pIn->Blue; + *pColors++ = pIn->Green; + *pColors++ = pIn->Red; + *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<double> SAL_CALL convertFromIntegerColorSpace( const uno::Sequence< ::sal_Int8 >& deviceColor, + const uno::Reference< rendering::XColorSpace >& targetColorSpace ) override + { + if( dynamic_cast<CairoColorSpace*>(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<rendering::XColorSpace*>(this), 0); + + uno::Sequence<double> aRes(nLen); + double* pOut( aRes.getArray() ); + for( std::size_t i=0; i<nLen; i+=4 ) + { + *pOut++ = vcl::unotools::toDoubleColor(*pIn++); + *pOut++ = vcl::unotools::toDoubleColor(*pIn++); + *pOut++ = vcl::unotools::toDoubleColor(*pIn++); + *pOut++ = vcl::unotools::toDoubleColor(*pIn++); + } + return aRes; + } + else + { + // TODO(P3): if we know anything about target + // colorspace, this can be greatly sped up + uno::Sequence<rendering::ARGBColor> 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<CairoColorSpace*>(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<rendering::ARGBColor> 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<rendering::XColorSpace*>(this), 0); + + uno::Sequence< rendering::RGBColor > aRes(nLen/4); + rendering::RGBColor* pOut( aRes.getArray() ); + for( std::size_t i=0; i<nLen; i+=4 ) + { + const double fAlpha(static_cast<sal_uInt8>(pIn[3])); + if( fAlpha ) + *pOut++ = rendering::RGBColor( + pIn[2]/fAlpha, + pIn[1]/fAlpha, + pIn[0]/fAlpha); + else + *pOut++ = rendering::RGBColor(0,0,0); + pIn += 4; + } + return aRes; + } + + virtual uno::Sequence< rendering::ARGBColor > 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<rendering::XColorSpace*>(this), 0); + + uno::Sequence< rendering::ARGBColor > aRes(nLen/4); + rendering::ARGBColor* pOut( aRes.getArray() ); + for( std::size_t i=0; i<nLen; i+=4 ) + { + const double fAlpha(static_cast<sal_uInt8>(pIn[3])); + if( fAlpha ) + *pOut++ = rendering::ARGBColor( + fAlpha/255.0, + pIn[2]/fAlpha, + pIn[1]/fAlpha, + pIn[0]/fAlpha); + else + *pOut++ = rendering::ARGBColor(0,0,0,0); + pIn += 4; + } + return aRes; + } + virtual uno::Sequence< rendering::ARGBColor > 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<rendering::XColorSpace*>(this), 0); + + uno::Sequence< rendering::ARGBColor > aRes(nLen/4); + rendering::ARGBColor* pOut( aRes.getArray() ); + for( std::size_t i=0; i<nLen; i+=4 ) + { + *pOut++ = rendering::ARGBColor( + vcl::unotools::toDoubleColor(pIn[3]), + vcl::unotools::toDoubleColor(pIn[2]), + vcl::unotools::toDoubleColor(pIn[1]), + vcl::unotools::toDoubleColor(pIn[0])); + pIn += 4; + } + return aRes; + } + + virtual uno::Sequence< ::sal_Int8 > 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; i<nLen; ++i ) + { + *pColors++ = vcl::unotools::toByteColor(pIn->Blue); + *pColors++ = vcl::unotools::toByteColor(pIn->Green); + *pColors++ = vcl::unotools::toByteColor(pIn->Red); + *pColors++ = -1; + ++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; i<nLen; ++i ) + { + const double fAlpha(pIn->Alpha); + *pColors++ = vcl::unotools::toByteColor(fAlpha*pIn->Blue); + *pColors++ = vcl::unotools::toByteColor(fAlpha*pIn->Green); + *pColors++ = vcl::unotools::toByteColor(fAlpha*pIn->Red); + *pColors++ = vcl::unotools::toByteColor(fAlpha); + ++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; i<nLen; ++i ) + { + *pColors++ = vcl::unotools::toByteColor(pIn->Blue); + *pColors++ = vcl::unotools::toByteColor(pIn->Green); + *pColors++ = vcl::unotools::toByteColor(pIn->Red); + *pColors++ = vcl::unotools::toByteColor(pIn->Alpha); + ++pIn; + } + return aRes; + } + + public: + CairoColorSpace() : + maComponentTags(4), + maBitCounts(4) + { + sal_Int8* pTags = maComponentTags.getArray(); + sal_Int32* pBitCounts = maBitCounts.getArray(); + pTags[0] = rendering::ColorComponentTag::RGB_BLUE; + pTags[1] = rendering::ColorComponentTag::RGB_GREEN; + pTags[2] = rendering::ColorComponentTag::RGB_RED; + pTags[3] = rendering::ColorComponentTag::PREMULTIPLIED_ALPHA; + + pBitCounts[0] = + pBitCounts[1] = + pBitCounts[2] = + pBitCounts[3] = 8; + } + }; + + class CairoNoAlphaColorSpace : 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<rendering::ARGBColor> 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<rendering::XColorSpace*>(this), 0); + + uno::Sequence< rendering::RGBColor > aRes(nLen/4); + rendering::RGBColor* pOut( aRes.getArray() ); + for( std::size_t i=0; i<nLen; i+=4 ) + { + *pOut++ = rendering::RGBColor(pIn[2], pIn[1], pIn[0]); + pIn += 4; + } + return aRes; + } + uno::Sequence< rendering::ARGBColor > impl_convertToARGB( const uno::Sequence< double >& deviceColor ) + { + 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<rendering::XColorSpace*>(this), 0); + + uno::Sequence< rendering::ARGBColor > aRes(nLen/4); + rendering::ARGBColor* pOut( aRes.getArray() ); + for( std::size_t i=0; i<nLen; i+=4 ) + { + *pOut++ = rendering::ARGBColor(1.0, pIn[2], pIn[1], pIn[0]); + pIn += 4; + } + return aRes; + } + virtual uno::Sequence< rendering::ARGBColor > SAL_CALL convertToARGB( const uno::Sequence< double >& deviceColor ) override + { + return impl_convertToARGB( deviceColor ); + } + virtual uno::Sequence< rendering::ARGBColor > SAL_CALL convertToPARGB( const uno::Sequence< double >& deviceColor ) override + { + return impl_convertToARGB( deviceColor ); + } + virtual uno::Sequence< double > 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; i<nLen; ++i ) + { + *pColors++ = pIn->Blue; + *pColors++ = pIn->Green; + *pColors++ = pIn->Red; + *pColors++ = 1.0; // the value does not matter + ++pIn; + } + return aRes; + } + uno::Sequence< double > impl_convertFromARGB( const uno::Sequence< rendering::ARGBColor >& rgbColor ) + { + 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; i<nLen; ++i ) + { + *pColors++ = pIn->Blue; + *pColors++ = pIn->Green; + *pColors++ = pIn->Red; + *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 + { + return impl_convertFromARGB( rgbColor ); + } + virtual uno::Sequence< double > SAL_CALL convertFromPARGB( const uno::Sequence< rendering::ARGBColor >& rgbColor ) override + { + return impl_convertFromARGB( rgbColor ); + } + + // 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<double> SAL_CALL convertFromIntegerColorSpace( const uno::Sequence< ::sal_Int8 >& deviceColor, + const uno::Reference< rendering::XColorSpace >& targetColorSpace ) override + { + if( dynamic_cast<CairoColorSpace*>(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<rendering::XColorSpace*>(this), 0); + + uno::Sequence<double> aRes(nLen); + double* pOut( aRes.getArray() ); + for( std::size_t i=0; i<nLen; i+=4 ) + { + *pOut++ = vcl::unotools::toDoubleColor(*pIn++); + *pOut++ = vcl::unotools::toDoubleColor(*pIn++); + *pOut++ = vcl::unotools::toDoubleColor(*pIn++); + *pOut++ = 1.0; pIn++; // the value does not matter + } + return aRes; + } + else + { + // TODO(P3): if we know anything about target + // colorspace, this can be greatly sped up + uno::Sequence<rendering::ARGBColor> 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<CairoNoAlphaColorSpace*>(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<rendering::ARGBColor> 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<rendering::XColorSpace*>(this), 0); + + uno::Sequence< rendering::RGBColor > aRes(nLen/4); + rendering::RGBColor* pOut( aRes.getArray() ); + for( std::size_t i=0; i<nLen; i+=4 ) + { + *pOut++ = rendering::RGBColor( pIn[2], pIn[1], pIn[0] ); + pIn += 4; + } + return aRes; + } + + virtual uno::Sequence< rendering::ARGBColor > SAL_CALL convertIntegerToARGB( const uno::Sequence< ::sal_Int8 >& deviceColor ) override + { + return impl_convertIntegerToARGB( deviceColor ); + } + virtual uno::Sequence< rendering::ARGBColor > SAL_CALL convertIntegerToPARGB( const uno::Sequence< ::sal_Int8 >& deviceColor ) override + { + return impl_convertIntegerToARGB( deviceColor ); + } + uno::Sequence< rendering::ARGBColor > impl_convertIntegerToARGB( const uno::Sequence< ::sal_Int8 >& deviceColor ) + { + 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<rendering::XColorSpace*>(this), 0); + + uno::Sequence< rendering::ARGBColor > aRes(nLen/4); + rendering::ARGBColor* pOut( aRes.getArray() ); + for( std::size_t i=0; i<nLen; i+=4 ) + { + *pOut++ = rendering::ARGBColor( + 1.0, + vcl::unotools::toDoubleColor(pIn[2]), + vcl::unotools::toDoubleColor(pIn[1]), + vcl::unotools::toDoubleColor(pIn[0])); + pIn += 4; + } + return aRes; + } + + virtual uno::Sequence< ::sal_Int8 > 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; i<nLen; ++i ) + { + *pColors++ = vcl::unotools::toByteColor(pIn->Blue); + *pColors++ = vcl::unotools::toByteColor(pIn->Green); + *pColors++ = vcl::unotools::toByteColor(pIn->Red); + *pColors++ = -1; // the value does not matter + ++pIn; + } + return aRes; + } + + virtual uno::Sequence< ::sal_Int8 > SAL_CALL convertIntegerFromARGB( const uno::Sequence< rendering::ARGBColor >& rgbColor ) override + { + return impl_convertIntegerFromARGB( rgbColor ); + } + virtual uno::Sequence< ::sal_Int8 > SAL_CALL convertIntegerFromPARGB( const uno::Sequence< rendering::ARGBColor >& rgbColor ) override + { + return impl_convertIntegerFromARGB( rgbColor ); + } + uno::Sequence< ::sal_Int8 > impl_convertIntegerFromARGB( const uno::Sequence< rendering::ARGBColor >& rgbColor ) + { + 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; i<nLen; ++i ) + { + *pColors++ = vcl::unotools::toByteColor(pIn->Blue); + *pColors++ = vcl::unotools::toByteColor(pIn->Green); + *pColors++ = vcl::unotools::toByteColor(pIn->Red); + *pColors++ = -1; // the value does not matter + ++pIn; + } + return aRes; + } + + public: + CairoNoAlphaColorSpace() : + maComponentTags(3), + maBitCounts(3) + { + sal_Int8* pTags = maComponentTags.getArray(); + sal_Int32* pBitCounts = maBitCounts.getArray(); + pTags[0] = rendering::ColorComponentTag::RGB_BLUE; + pTags[1] = rendering::ColorComponentTag::RGB_GREEN; + pTags[2] = rendering::ColorComponentTag::RGB_RED; + + pBitCounts[0] = + pBitCounts[1] = + pBitCounts[2] = 8; + } + }; + + uno::Reference<rendering::XIntegerBitmapColorSpace>& GetCairoNoAlphaColorSpace() + { + static uno::Reference<rendering::XIntegerBitmapColorSpace> SPACE = new CairoNoAlphaColorSpace(); + return SPACE; + }; + + uno::Reference<rendering::XIntegerBitmapColorSpace>& GetCairoColorSpace() + { + static uno::Reference<rendering::XIntegerBitmapColorSpace> SPACE = new CairoColorSpace(); + return SPACE; + }; + + } + + rendering::IntegerBitmapLayout CanvasHelper::getMemoryLayout() + { + if( !mpCairo ) + return rendering::IntegerBitmapLayout(); // we're disposed + + const geometry::IntegerSize2D aSize(getSize()); + + return impl_getMemoryLayout( aSize.Width, aSize.Height ); + } + + rendering::IntegerBitmapLayout + CanvasHelper::impl_getMemoryLayout( const sal_Int32 nWidth, const sal_Int32 nHeight ) + { + rendering::IntegerBitmapLayout aLayout; + + aLayout.ScanLines = nHeight; + aLayout.ScanLineBytes = nWidth*4; + aLayout.ScanLineStride = aLayout.ScanLineBytes; + aLayout.PlaneStride = 0; + aLayout.ColorSpace = mbHaveAlpha ? GetCairoColorSpace() : GetCairoNoAlphaColorSpace(); + aLayout.Palette.clear(); + aLayout.IsMsbFirst = false; + + return aLayout; + } + + + bool CanvasHelper::repaint( const SurfaceSharedPtr& pSurface, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState ) + { + SAL_INFO( "canvas.cairo", "CanvasHelper::repaint"); + + if( !mpCairo ) + return true; + + cairo_save( mpCairo.get() ); + + cairo_rectangle( mpCairo.get(), 0, 0, maSize.getWidth(), maSize.getHeight() ); + cairo_clip( mpCairo.get() ); + + useStates( viewState, renderState, true ); + + cairo_matrix_t aMatrix; + + cairo_get_matrix( mpCairo.get(), &aMatrix ); + aMatrix.xx = aMatrix.yy = 1; + cairo_set_matrix( mpCairo.get(), &aMatrix ); + + cairo_set_source_surface( mpCairo.get(), pSurface->getCairoSurface().get(), 0, 0 ); + cairo_paint( mpCairo.get() ); + cairo_restore( mpCairo.get() ); + + return true; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/cairo/cairo_canvashelper.hxx b/canvas/source/cairo/cairo_canvashelper.hxx new file mode 100644 index 0000000000..21dbf79d77 --- /dev/null +++ b/canvas/source/cairo/cairo_canvashelper.hxx @@ -0,0 +1,271 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/geometry/IntegerPoint2D.hpp> +#include <com/sun/star/geometry/IntegerRectangle2D.hpp> +#include <com/sun/star/rendering/IntegerBitmapLayout.hpp> +#include <com/sun/star/rendering/XCanvas.hpp> + +#include <vcl/vclptr.hxx> +#include <vcl/virdev.hxx> + +#include <vcl/cairo.hxx> +#include "cairo_surfaceprovider.hxx" + +class VirtualDevice; + +namespace basegfx { + class B2DPolyPolygon; +} + +namespace cairocanvas +{ + class SpriteCanvas; + + enum Operation { + Stroke, + Fill, + Clip + }; + + class CanvasHelper + { + public: + /// make noncopyable + CanvasHelper(const CanvasHelper&) = delete; + const CanvasHelper& operator=(const CanvasHelper&) = delete; + + CanvasHelper(); + + /// Release all references + void disposing(); + + /** Initialize canvas helper + + This method late-initializes the canvas helper, providing + it with the necessary device and size. Note that the + CanvasHelper does <em>not</em> take ownership of the + passed rDevice reference, nor does it perform any + reference counting. Thus, to prevent the reference counted + SpriteCanvas object from deletion, the user of this class + is responsible for holding ref-counted references itself! + + @param rSizePixel + Size of the output surface in pixel. + + @param rDevice + Reference device this canvas is associated with + + */ + void init( const ::basegfx::B2ISize& rSizePixel, + SurfaceProvider& rSurfaceProvider, + css::rendering::XGraphicDevice* pDevice ); + + void setSize( const ::basegfx::B2ISize& rSize ); + void setSurface( const ::cairo::SurfaceSharedPtr& pSurface, bool bHasAlpha ); + + // CanvasHelper functionality + // ========================== + + // XCanvas (only providing, not implementing the + // interface. Also note subtle method parameter differences) + void clear(); + void drawLine( const css::rendering::XCanvas* pCanvas, + const css::geometry::RealPoint2D& aStartPoint, + const css::geometry::RealPoint2D& aEndPoint, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState ); + void drawBezier( const css::rendering::XCanvas* pCanvas, + const css::geometry::RealBezierSegment2D& aBezierSegment, + const css::geometry::RealPoint2D& aEndPoint, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState ); + css::uno::Reference< css::rendering::XCachedPrimitive > + drawPolyPolygon( const css::rendering::XCanvas* pCanvas, + const css::uno::Reference< css::rendering::XPolyPolygon2D >& xPolyPolygon, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState ); + css::uno::Reference< css::rendering::XCachedPrimitive > + strokePolyPolygon( const css::rendering::XCanvas* pCanvas, + const css::uno::Reference< + css::rendering::XPolyPolygon2D >& xPolyPolygon, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState, + const css::rendering::StrokeAttributes& strokeAttributes ); + css::uno::Reference< css::rendering::XCachedPrimitive > + strokeTexturedPolyPolygon( const css::rendering::XCanvas* pCanvas, + const css::uno::Reference< css::rendering::XPolyPolygon2D >& xPolyPolygon, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState, + const css::uno::Sequence< css::rendering::Texture >& textures, + const css::rendering::StrokeAttributes& strokeAttributes ); + css::uno::Reference< css::rendering::XCachedPrimitive > + strokeTextureMappedPolyPolygon( const css::rendering::XCanvas* pCanvas, + const css::uno::Reference< css::rendering::XPolyPolygon2D >& xPolyPolygon, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState, + const css::uno::Sequence< css::rendering::Texture >& textures, + const css::uno::Reference< css::geometry::XMapping2D >& xMapping, + const css::rendering::StrokeAttributes& strokeAttributes ); + css::uno::Reference< css::rendering::XPolyPolygon2D > + queryStrokeShapes( const css::rendering::XCanvas* pCanvas, + const css::uno::Reference< css::rendering::XPolyPolygon2D >& xPolyPolygon, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState, + const css::rendering::StrokeAttributes& strokeAttributes ); + css::uno::Reference< css::rendering::XCachedPrimitive > + fillPolyPolygon( const css::rendering::XCanvas* pCanvas, + const css::uno::Reference< css::rendering::XPolyPolygon2D >& xPolyPolygon, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState ); + css::uno::Reference< css::rendering::XCachedPrimitive > + fillTexturedPolyPolygon( const css::rendering::XCanvas* pCanvas, + const css::uno::Reference< css::rendering::XPolyPolygon2D >& xPolyPolygon, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState, + const css::uno::Sequence< css::rendering::Texture >& textures ); + css::uno::Reference< css::rendering::XCachedPrimitive > + fillTextureMappedPolyPolygon( const css::rendering::XCanvas* pCanvas, + const css::uno::Reference< css::rendering::XPolyPolygon2D >& xPolyPolygon, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState, + const css::uno::Sequence< css::rendering::Texture >& textures, + const css::uno::Reference< css::geometry::XMapping2D >& xMapping ); + + css::uno::Reference< css::rendering::XCanvasFont > + createFont( const css::rendering::XCanvas* pCanvas, + const css::rendering::FontRequest& fontRequest, + const css::uno::Sequence< css::beans::PropertyValue >& extraFontProperties, + const css::geometry::Matrix2D& fontMatrix ); + + css::uno::Sequence< css::rendering::FontInfo > + queryAvailableFonts( const css::rendering::XCanvas* pCanvas, + const css::rendering::FontInfo& aFilter, + const css::uno::Sequence< css::beans::PropertyValue >& aFontProperties ); + + css::uno::Reference< css::rendering::XCachedPrimitive > + drawText( const css::rendering::XCanvas* pCanvas, + const css::rendering::StringContext& text, + const css::uno::Reference< css::rendering::XCanvasFont >& xFont, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState, + sal_Int8 textDirection ); + + css::uno::Reference< css::rendering::XCachedPrimitive > + drawTextLayout( const css::rendering::XCanvas* pCanvas, + const css::uno::Reference< css::rendering::XTextLayout >& laidOutText, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState ); + + css::uno::Reference< css::rendering::XCachedPrimitive > + drawBitmap( const css::rendering::XCanvas* pCanvas, + const css::uno::Reference< css::rendering::XBitmap >& xBitmap, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState ); + css::uno::Reference< css::rendering::XCachedPrimitive > + drawBitmapModulated( const css::rendering::XCanvas* pCanvas, + const css::uno::Reference< css::rendering::XBitmap >& xBitmap, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState ); + css::uno::Reference< css::rendering::XGraphicDevice > + getDevice() { return css::uno::Reference< css::rendering::XGraphicDevice >(mpDevice); } + + // BitmapCanvasHelper functionality + // ================================ + + css::geometry::IntegerSize2D getSize() const; + + css::uno::Reference< css::rendering::XBitmap > + getScaledBitmap( const css::geometry::RealSize2D& newSize, + bool beFast ); + + css::uno::Sequence< sal_Int8 > + getData( css::rendering::IntegerBitmapLayout& bitmapLayout, + const css::geometry::IntegerRectangle2D& rect ); + + css::uno::Sequence< sal_Int8 > + getPixel( css::rendering::IntegerBitmapLayout& bitmapLayout, + const css::geometry::IntegerPoint2D& pos ); + + css::rendering::IntegerBitmapLayout getMemoryLayout(); + + void doPolyPolygonPath( const css::uno::Reference< css::rendering::XPolyPolygon2D >& xPolyPolygon, + Operation aOperation, + bool bNoLineJoin = false, + const css::uno::Sequence< css::rendering::Texture >* pTextures=nullptr ) const; + + css::uno::Reference< css::rendering::XCachedPrimitive > implDrawBitmapSurface( + const css::rendering::XCanvas* pCanvas, + const ::cairo::SurfaceSharedPtr& pSurface, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState, + const css::geometry::IntegerSize2D& rSize, + bool bModulateColors, + bool bHasAlpha ); + + bool repaint( const ::cairo::SurfaceSharedPtr& pSurface, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState ); + + protected: + /** Surface provider + + Deliberately not a refcounted reference, because of + potential circular references for canvas. Provides us with + our output surface and associated functionality. + */ + SurfaceProvider* mpSurfaceProvider; + + /** Phyical output device + + Deliberately not a refcounted reference, because of + potential circular references for spritecanvas. + */ + css::rendering::XGraphicDevice* mpDevice; + + private: + + VclPtr<VirtualDevice> mpVirtualDevice; + + void useStates( const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState, + bool setColor ); + + css::rendering::IntegerBitmapLayout impl_getMemoryLayout( sal_Int32 nWidth, sal_Int32 nHeight ); + + /// When true, content is able to represent alpha + bool mbHaveAlpha; + + ::cairo::CairoSharedPtr mpCairo; + ::cairo::SurfaceSharedPtr mpSurface; + ::basegfx::B2ISize maSize; + }; + + /// also needed from SpriteHelper + void doPolyPolygonImplementation( const ::basegfx::B2DPolyPolygon& aPolyPolygon, + Operation aOperation, + cairo_t* pCairo, + const css::uno::Sequence< css::rendering::Texture >* pTextures, + const SurfaceProviderRef& pDevice, + css::rendering::FillRule eFillrule ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/cairo/cairo_canvashelper_text.cxx b/canvas/source/cairo/cairo_canvashelper_text.cxx new file mode 100644 index 0000000000..c8498bddf3 --- /dev/null +++ b/canvas/source/cairo/cairo_canvashelper_text.cxx @@ -0,0 +1,303 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <com/sun/star/rendering/TextDirection.hpp> + +#include <rtl/math.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <vcl/canvastools.hxx> +#include <vcl/metric.hxx> +#include <vcl/virdev.hxx> + +#include <canvas/canvastools.hxx> +#include <verifyinput.hxx> + +#include "cairo_canvasfont.hxx" +#include "cairo_canvashelper.hxx" +#include "cairo_textlayout.hxx" + +using namespace ::cairo; +using namespace ::com::sun::star; + +namespace cairocanvas +{ + uno::Reference< rendering::XCanvasFont > CanvasHelper::createFont( const rendering::XCanvas* , + const rendering::FontRequest& fontRequest, + const uno::Sequence< beans::PropertyValue >& extraFontProperties, + const geometry::Matrix2D& fontMatrix ) + { + return uno::Reference< rendering::XCanvasFont >( new CanvasFont( fontRequest, extraFontProperties, fontMatrix, mpSurfaceProvider )); + } + + uno::Sequence< rendering::FontInfo > CanvasHelper::queryAvailableFonts( const rendering::XCanvas* , + const rendering::FontInfo& /*aFilter*/, + const uno::Sequence< beans::PropertyValue >& /*aFontProperties*/ ) + { + // TODO + return uno::Sequence< rendering::FontInfo >(); + } + + static bool + setupFontTransform( ::OutputDevice const & rOutDev, + ::Point& o_rPoint, + vcl::Font& io_rVCLFont, + const rendering::ViewState& rViewState, + const rendering::RenderState& rRenderState ) + { + ::basegfx::B2DHomMatrix aMatrix; + + ::canvas::tools::mergeViewAndRenderTransform(aMatrix, + rViewState, + rRenderState); + + ::basegfx::B2DTuple aScale; + ::basegfx::B2DTuple aTranslate; + double nRotate, nShearX; + + aMatrix.decompose( aScale, aTranslate, nRotate, nShearX ); + + // query font metric _before_ tampering with width and height + if( !::rtl::math::approxEqual(aScale.getX(), aScale.getY()) ) + { + // retrieve true font width + const sal_Int32 nFontWidth( rOutDev.GetFontMetric( io_rVCLFont ).GetAverageFontWidth() ); + + const sal_Int32 nScaledFontWidth( ::basegfx::fround(nFontWidth * aScale.getX()) ); + + if( !nScaledFontWidth ) + { + // scale is smaller than one pixel - disable text + // output altogether + return false; + } + + io_rVCLFont.SetAverageFontWidth( nScaledFontWidth ); + } + + if( !::rtl::math::approxEqual(aScale.getY(), 1.0) ) + { + const sal_Int32 nFontHeight( io_rVCLFont.GetFontHeight() ); + io_rVCLFont.SetFontHeight( ::basegfx::fround(nFontHeight * aScale.getY()) ); + } + + io_rVCLFont.SetOrientation( Degree10( ::basegfx::fround(-basegfx::rad2deg<10>(fmod(nRotate, 2*M_PI))) ) ); + + // TODO(F2): Missing functionality in VCL: shearing + o_rPoint.setX( ::basegfx::fround(aTranslate.getX()) ); + o_rPoint.setY( ::basegfx::fround(aTranslate.getY()) ); + + return true; + } + + static void + setupOutDevState( OutputDevice& rOutDev, + const rendering::XCanvas* pOwner, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState ) + { + ::canvas::tools::verifyInput( renderState, + __func__, + const_cast<rendering::XCanvas*>(pOwner), // only for refcount + 2, + 3 /* text */ ); + + // TODO(P2): Don't change clipping all the time, maintain current clip + // state and change only when update is necessary + ::canvas::tools::clipOutDev(viewState, renderState, rOutDev); + + Color aColor( COL_WHITE ); + + if( renderState.DeviceColor.getLength() > 2 ) + { + aColor = vcl::unotools::stdColorSpaceSequenceToColor( renderState.DeviceColor ); + } + + // extract alpha, and make color opaque + // afterwards. Otherwise, OutputDevice won't draw anything + aColor.SetAlpha(255); + + rOutDev.SetTextColor( aColor ); + } + + namespace { + + class DeviceSettingsGuard + { + private: + VclPtr<OutputDevice> mpVirtualDevice; + bool mbMappingWasEnabled; + public: + DeviceSettingsGuard(OutputDevice *pVirtualDevice) + : mpVirtualDevice(pVirtualDevice) + , mbMappingWasEnabled(mpVirtualDevice->IsMapModeEnabled()) + { + mpVirtualDevice->Push(); + mpVirtualDevice->EnableMapMode(false); + } + + ~DeviceSettingsGuard() + { + mpVirtualDevice->EnableMapMode(mbMappingWasEnabled); + mpVirtualDevice->Pop(); + } + }; + + } + + static bool setupTextOutput( OutputDevice& rOutDev, + const rendering::XCanvas* pOwner, + ::Point& o_rOutPos, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState, + const uno::Reference< rendering::XCanvasFont >& xFont ) + { + setupOutDevState( rOutDev, pOwner, viewState, renderState ); + + CanvasFont* pFont = dynamic_cast< CanvasFont* >( xFont.get() ); + + ENSURE_ARG_OR_THROW( pFont, + "CanvasHelper::setupTextOutput(): Font not compatible with this canvas" ); + + vcl::Font aVCLFont = pFont->getVCLFont(); + + Color aColor( COL_BLACK ); + + if( renderState.DeviceColor.getLength() > 2 ) + { + aColor = vcl::unotools::stdColorSpaceSequenceToColor(renderState.DeviceColor ); + } + + // setup font color + aVCLFont.SetColor( aColor ); + aVCLFont.SetFillColor( aColor ); + + if (pFont->getEmphasisMark()) + aVCLFont.SetEmphasisMark(FontEmphasisMark(pFont->getEmphasisMark())); + + // no need to replicate this for mp2ndOutDev, we're modifying only aVCLFont here. + if( !setupFontTransform( rOutDev, o_rOutPos, aVCLFont, viewState, renderState ) ) + return false; + + rOutDev.SetFont( aVCLFont ); + + return true; + } + + uno::Reference< rendering::XCachedPrimitive > CanvasHelper::drawText( const rendering::XCanvas* pOwner, + const rendering::StringContext& text, + const uno::Reference< rendering::XCanvasFont >& xFont, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState, + sal_Int8 textDirection ) + { +#ifdef CAIRO_CANVAS_PERF_TRACE + struct timespec aTimer; + mxDevice->startPerfTrace( &aTimer ); +#endif + + ENSURE_ARG_OR_THROW( xFont.is(), + "CanvasHelper::drawText(): font is NULL"); + + if( !mpVirtualDevice ) + mpVirtualDevice = mpSurface->createVirtualDevice(); + + if( mpVirtualDevice ) + { + DeviceSettingsGuard aGuard(mpVirtualDevice.get()); + + ::Point aOutpos; + if( !setupTextOutput( *mpVirtualDevice, pOwner, aOutpos, viewState, renderState, xFont ) ) + return uno::Reference< rendering::XCachedPrimitive >(nullptr); // no output necessary + + // change text direction and layout mode + vcl::text::ComplexTextLayoutFlags nLayoutMode(vcl::text::ComplexTextLayoutFlags::Default); + switch( textDirection ) + { + case rendering::TextDirection::WEAK_LEFT_TO_RIGHT: + case rendering::TextDirection::STRONG_LEFT_TO_RIGHT: + nLayoutMode |= vcl::text::ComplexTextLayoutFlags::BiDiStrong; + nLayoutMode |= vcl::text::ComplexTextLayoutFlags::TextOriginLeft; + break; + + case rendering::TextDirection::WEAK_RIGHT_TO_LEFT: + nLayoutMode |= vcl::text::ComplexTextLayoutFlags::BiDiRtl; + [[fallthrough]]; + case rendering::TextDirection::STRONG_RIGHT_TO_LEFT: + nLayoutMode |= vcl::text::ComplexTextLayoutFlags::BiDiRtl | vcl::text::ComplexTextLayoutFlags::BiDiStrong; + nLayoutMode |= vcl::text::ComplexTextLayoutFlags::TextOriginRight; + break; + } + + // TODO(F2): alpha + mpVirtualDevice->SetLayoutMode( nLayoutMode ); + + rtl::Reference pTextLayout( new TextLayout(text, textDirection, 0, CanvasFont::Reference(dynamic_cast< CanvasFont* >( xFont.get() )), mpSurfaceProvider) ); + pTextLayout->draw(*mpVirtualDevice, aOutpos, viewState, renderState); + } + + return uno::Reference< rendering::XCachedPrimitive >(nullptr); + } + + uno::Reference< rendering::XCachedPrimitive > CanvasHelper::drawTextLayout( const rendering::XCanvas* pOwner, + const uno::Reference< rendering::XTextLayout >& xLayoutedText, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState ) + { + ENSURE_ARG_OR_THROW( xLayoutedText.is(), + "CanvasHelper::drawTextLayout(): layout is NULL"); + + TextLayout* pTextLayout = dynamic_cast< TextLayout* >( xLayoutedText.get() ); + + if( pTextLayout ) + { + if( !mpVirtualDevice ) + mpVirtualDevice = mpSurface->createVirtualDevice(); + + if( mpVirtualDevice ) + { + DeviceSettingsGuard aGuard(mpVirtualDevice.get()); + + // TODO(T3): Race condition. We're taking the font + // from xLayoutedText, and then calling draw() at it, + // without exclusive access. Move setupTextOutput(), + // e.g. to impltools? + + ::Point aOutpos; + if( !setupTextOutput( *mpVirtualDevice, pOwner, aOutpos, viewState, renderState, xLayoutedText->getFont() ) ) + return uno::Reference< rendering::XCachedPrimitive >(nullptr); // no output necessary + + // TODO(F2): What about the offset scalings? + pTextLayout->draw(*mpVirtualDevice, aOutpos, viewState, renderState); + } + } + else + { + ENSURE_ARG_OR_THROW( false, + "CanvasHelper::drawTextLayout(): TextLayout not compatible with this canvas" ); + } + + return uno::Reference< rendering::XCachedPrimitive >(nullptr); + } + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/cairo/cairo_devicehelper.cxx b/canvas/source/cairo/cairo_devicehelper.cxx new file mode 100644 index 0000000000..3d3f7ef830 --- /dev/null +++ b/canvas/source/cairo/cairo_devicehelper.cxx @@ -0,0 +1,257 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> +#include <sal/log.hxx> + +#include <basegfx/utils/canvastools.hxx> +#include <basegfx/utils/unopolypolygon.hxx> +#include <tools/stream.hxx> +#include <vcl/bitmapex.hxx> +#include <vcl/canvastools.hxx> +#include <vcl/dibtools.hxx> + +#include <canvas/canvastools.hxx> + +#include "cairo_canvasbitmap.hxx" +#include "cairo_devicehelper.hxx" + +using namespace ::cairo; +using namespace ::com::sun::star; + +namespace cairocanvas +{ + DeviceHelper::DeviceHelper() : + mpSurfaceProvider( nullptr ), + mpRefDevice( nullptr ) + { + } + + void DeviceHelper::implInit( SurfaceProvider& rSurfaceProvider, + OutputDevice& rRefDevice ) + { + mpSurfaceProvider = &rSurfaceProvider; + mpRefDevice = &rRefDevice; + + // no own surface, this is handled by derived classes + } + + void DeviceHelper::init( SurfaceProvider& rSurfaceProvider, + OutputDevice& rRefDevice ) + { + implInit(rSurfaceProvider, rRefDevice); + + OutputDevice* pOutDev = getOutputDevice(); + mpSurface = pOutDev->CreateSurface(pOutDev->GetOutOffXPixel(), + pOutDev->GetOutOffYPixel(), + pOutDev->GetOutputWidthPixel(), + pOutDev->GetOutputHeightPixel()); + } + + void DeviceHelper::disposing() + { + // release all references + mpSurface.reset(); + mpRefDevice = nullptr; + mpSurfaceProvider = nullptr; + } + + void DeviceHelper::setSize( const ::basegfx::B2ISize& rSize ) + { + SAL_INFO( + "canvas.cairo", + "device size " << rSize.getWidth() << " x " << rSize.getHeight()); + + if( !mpRefDevice ) + return; // disposed + + OutputDevice* pOutDev = getOutputDevice(); + + // X11 only + bool bReuseSurface = mpSurface && + mpSurface->Resize(rSize.getWidth() + pOutDev->GetOutOffXPixel(), + rSize.getHeight() + pOutDev->GetOutOffYPixel()); + + if (!bReuseSurface) + { + mpSurface = pOutDev->CreateSurface( + pOutDev->GetOutOffXPixel(), + pOutDev->GetOutOffYPixel(), + rSize.getWidth(), rSize.getHeight() ); + } + } + + geometry::RealSize2D DeviceHelper::getPhysicalResolution() + { + // Map a one-by-one millimeter box to pixel + const MapMode aOldMapMode( mpRefDevice->GetMapMode() ); + mpRefDevice->SetMapMode( MapMode(MapUnit::MapMM) ); + const Size aPixelSize( mpRefDevice->LogicToPixel(Size(1,1)) ); + mpRefDevice->SetMapMode( aOldMapMode ); + + return vcl::unotools::size2DFromSize( aPixelSize ); + } + + geometry::RealSize2D DeviceHelper::getPhysicalSize() + { + if( !mpRefDevice ) + return ::canvas::tools::createInfiniteSize2D(); // we're disposed + + // Map the pixel dimensions of the output window to millimeter + const MapMode aOldMapMode( mpRefDevice->GetMapMode() ); + mpRefDevice->SetMapMode( MapMode(MapUnit::MapMM) ); + const Size aLogSize( mpRefDevice->PixelToLogic(mpRefDevice->GetOutputSizePixel()) ); + mpRefDevice->SetMapMode( aOldMapMode ); + + return vcl::unotools::size2DFromSize( aLogSize ); + } + + uno::Reference< rendering::XLinePolyPolygon2D > DeviceHelper::createCompatibleLinePolyPolygon( + const uno::Reference< rendering::XGraphicDevice >& , + const uno::Sequence< uno::Sequence< geometry::RealPoint2D > >& points ) + { + // disposed? + if( !mpSurfaceProvider ) + return uno::Reference< rendering::XLinePolyPolygon2D >(); // we're disposed + + return uno::Reference< rendering::XLinePolyPolygon2D >( + new ::basegfx::unotools::UnoPolyPolygon( + ::basegfx::unotools::polyPolygonFromPoint2DSequenceSequence( points ) ) ); + } + + uno::Reference< rendering::XBezierPolyPolygon2D > DeviceHelper::createCompatibleBezierPolyPolygon( + const uno::Reference< rendering::XGraphicDevice >& , + const uno::Sequence< uno::Sequence< geometry::RealBezierSegment2D > >& points ) + { + // disposed? + if( !mpSurfaceProvider ) + return uno::Reference< rendering::XBezierPolyPolygon2D >(); // we're disposed + + return uno::Reference< rendering::XBezierPolyPolygon2D >( + new ::basegfx::unotools::UnoPolyPolygon( + ::basegfx::unotools::polyPolygonFromBezier2DSequenceSequence( points ) ) ); + } + + uno::Reference< rendering::XBitmap > DeviceHelper::createCompatibleBitmap( + const uno::Reference< rendering::XGraphicDevice >& rDevice, + const geometry::IntegerSize2D& size ) + { + // disposed? + if( !mpSurfaceProvider ) + return uno::Reference< rendering::XBitmap >(); // we're disposed + + return uno::Reference< rendering::XBitmap >( + new CanvasBitmap( + ::basegfx::unotools::b2ISizeFromIntegerSize2D( size ), + SurfaceProviderRef(mpSurfaceProvider), + rDevice.get(), + false )); + } + + uno::Reference< rendering::XVolatileBitmap > DeviceHelper::createVolatileBitmap( + const uno::Reference< rendering::XGraphicDevice >& , + const geometry::IntegerSize2D& /*size*/ ) + { + return uno::Reference< rendering::XVolatileBitmap >(); + } + + uno::Reference< rendering::XBitmap > DeviceHelper::createCompatibleAlphaBitmap( + const uno::Reference< rendering::XGraphicDevice >& rDevice, + const geometry::IntegerSize2D& size ) + { + // disposed? + if( !mpSurfaceProvider ) + return uno::Reference< rendering::XBitmap >(); // we're disposed + + return uno::Reference< rendering::XBitmap >( + new CanvasBitmap( + ::basegfx::unotools::b2ISizeFromIntegerSize2D( size ), + SurfaceProviderRef(mpSurfaceProvider), + rDevice.get(), + true )); + } + + uno::Reference< rendering::XVolatileBitmap > DeviceHelper::createVolatileAlphaBitmap( + const uno::Reference< rendering::XGraphicDevice >& , + const geometry::IntegerSize2D& /*size*/ ) + { + return uno::Reference< rendering::XVolatileBitmap >(); + } + + uno::Any DeviceHelper::isAccelerated() const + { + return css::uno::Any(false); + } + + uno::Any DeviceHelper::getDeviceHandle() const + { + return uno::Any( reinterpret_cast< sal_Int64 >(mpRefDevice.get()) ); + } + + uno::Any DeviceHelper::getSurfaceHandle() const + { + return uno::Any(); + } + + uno::Reference<rendering::XColorSpace> const & DeviceHelper::getColorSpace() const + { + static uno::Reference<rendering::XColorSpace> SPACE = vcl::unotools::createStandardColorSpace(); + // always the same + return SPACE; + } + + void DeviceHelper::dumpScreenContent() const + { + static sal_Int32 nFilePostfixCount(0); + + if( !mpRefDevice ) + return; + + OUString aFilename = "dbg_frontbuffer" + OUString::number(nFilePostfixCount) + ".bmp"; + + SvFileStream aStream( aFilename, StreamMode::STD_READWRITE ); + + const ::Point aEmptyPoint; + bool bOldMap( mpRefDevice->IsMapModeEnabled() ); + mpRefDevice->EnableMapMode( false ); + const ::BitmapEx aTempBitmap(mpRefDevice->GetBitmapEx(aEmptyPoint, mpRefDevice->GetOutputSizePixel())); + WriteDIB(aTempBitmap, aStream, false); + mpRefDevice->EnableMapMode( bOldMap ); + + ++nFilePostfixCount; + } + + SurfaceSharedPtr DeviceHelper::createSurface( const ::basegfx::B2ISize& rSize, int aContent ) + { + if( mpSurface ) + return mpSurface->getSimilar( aContent, rSize.getWidth(), rSize.getHeight() ); + + return SurfaceSharedPtr(); + } + + SurfaceSharedPtr DeviceHelper::createSurface( BitmapSystemData const & rData, const Size& rSize ) + { + if (mpRefDevice) + return mpRefDevice->CreateBitmapSurface(rData, rSize); + + return SurfaceSharedPtr(); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/cairo/cairo_devicehelper.hxx b/canvas/source/cairo/cairo_devicehelper.hxx new file mode 100644 index 0000000000..eede77844d --- /dev/null +++ b/canvas/source/cairo/cairo_devicehelper.hxx @@ -0,0 +1,122 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/rendering/XGraphicDevice.hpp> + +#include <vcl/outdev.hxx> + +#include "cairo_surfaceprovider.hxx" + +/* Definition of DeviceHelper class */ + +namespace cairocanvas +{ + class DeviceHelper + { + public: + /// make noncopyable + DeviceHelper(const DeviceHelper&) = delete; + const DeviceHelper& operator=(const DeviceHelper&) = delete; + + DeviceHelper(); + + /** init helper + + @param rCanvas + Owning canvas. + + @param rRefDevice + Reference output device. Needed for resolution + calculations etc. + */ + void init( SurfaceProvider& rSurfaceProvider, + OutputDevice& rRefDevice ); + + /// Dispose all internal references + void disposing(); + + // XWindowGraphicDevice + css::geometry::RealSize2D getPhysicalResolution(); + css::geometry::RealSize2D getPhysicalSize(); + css::uno::Reference< css::rendering::XLinePolyPolygon2D > createCompatibleLinePolyPolygon( + const css::uno::Reference< css::rendering::XGraphicDevice >& rDevice, + const css::uno::Sequence< css::uno::Sequence< css::geometry::RealPoint2D > >& points ); + css::uno::Reference< css::rendering::XBezierPolyPolygon2D > createCompatibleBezierPolyPolygon( + const css::uno::Reference< css::rendering::XGraphicDevice >& rDevice, + const css::uno::Sequence< css::uno::Sequence< css::geometry::RealBezierSegment2D > >& points ); + css::uno::Reference< css::rendering::XBitmap > createCompatibleBitmap( + const css::uno::Reference< css::rendering::XGraphicDevice >& rDevice, + const css::geometry::IntegerSize2D& size ); + css::uno::Reference< css::rendering::XVolatileBitmap > createVolatileBitmap( + const css::uno::Reference< css::rendering::XGraphicDevice >& rDevice, + const css::geometry::IntegerSize2D& size ); + css::uno::Reference< css::rendering::XBitmap > createCompatibleAlphaBitmap( + const css::uno::Reference< css::rendering::XGraphicDevice >& rDevice, + const css::geometry::IntegerSize2D& size ); + css::uno::Reference< css::rendering::XVolatileBitmap > createVolatileAlphaBitmap( + const css::uno::Reference< css::rendering::XGraphicDevice >& rDevice, + const css::geometry::IntegerSize2D& size ); + + css::uno::Any isAccelerated() const; + css::uno::Any getDeviceHandle() const; + css::uno::Any getSurfaceHandle() const; + css::uno::Reference< + css::rendering::XColorSpace > const & getColorSpace() const; + + /** called when DumpScreenContent property is enabled on + XGraphicDevice, and writes out bitmaps of current screen. + */ + void dumpScreenContent() const; + + OutputDevice* getOutputDevice() const { return mpRefDevice; } + const ::cairo::SurfaceSharedPtr& getSurface() const { return mpSurface; } + ::cairo::SurfaceSharedPtr createSurface( const ::basegfx::B2ISize& rSize, int aContent ); + ::cairo::SurfaceSharedPtr createSurface( BitmapSystemData const & rData, const Size& rSize ); + + protected: + /** init helper + + @param rCanvas + Owning canvas. + + @param rRefDevice + Reference output device. Needed for resolution + calculations etc. + */ + void implInit( SurfaceProvider& rSurfaceProvider, + OutputDevice& rRefDevice ); + void setSize( const ::basegfx::B2ISize& rSize ); + + private: + /** Surface provider + + Deliberately not a refcounted reference, because of + potential circular references for canvas. Provides us with + our output surface and associated functionality. + */ + SurfaceProvider* mpSurfaceProvider; + + VclPtr<OutputDevice> mpRefDevice; + ::cairo::SurfaceSharedPtr mpSurface; + }; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/cairo/cairo_repainttarget.hxx b/canvas/source/cairo/cairo_repainttarget.hxx new file mode 100644 index 0000000000..94d3d3845b --- /dev/null +++ b/canvas/source/cairo/cairo_repainttarget.hxx @@ -0,0 +1,48 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/rendering/RenderState.hpp> +#include <com/sun/star/rendering/ViewState.hpp> + +#include <vcl/cairo.hxx> + +namespace cairocanvas +{ + /* Definition of RepaintTarget interface */ + + /** Target interface for XCachedPrimitive implementations + + This interface must be implemented on all canvas + implementations that hand out XCachedPrimitives + */ + class SAL_LOPLUGIN_ANNOTATE("crosscast") RepaintTarget + { + public: + virtual ~RepaintTarget() {} + + // call this when a bitmap is repainted + virtual bool repaint( const ::cairo::SurfaceSharedPtr& pSurface, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState ) = 0; + }; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/cairo/cairo_sprite.hxx b/canvas/source/cairo/cairo_sprite.hxx new file mode 100644 index 0000000000..513283e308 --- /dev/null +++ b/canvas/source/cairo/cairo_sprite.hxx @@ -0,0 +1,65 @@ +/* -*- 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 . + */ + +#pragma once + +#include <base/sprite.hxx> + +#include <vcl/cairo.hxx> + +namespace cairocanvas +{ + /** Specialization of ::canvas::Sprite interface, to also provide + redraw methods. + */ + class Sprite : public ::canvas::Sprite + { + public: + + /** Redraw sprite at the stored position. + + @param bBufferedUpdate + When true, the redraw does <em>not</em> happen directly on + the front buffer, but within a VDev. Used to speed up + drawing. + */ + virtual void redraw( const ::cairo::CairoSharedPtr& pCairo, + bool bBufferedUpdate ) const = 0; + + /** Redraw sprite at the given position. + + @param rPos + Output position of the sprite. Overrides the sprite's own + output position. + + @param bBufferedUpdate + When true, the redraw does <em>not</em> happen directly on + the front buffer, but within a VDev. Used to speed up + drawing. + */ + virtual void redraw( const ::cairo::CairoSharedPtr& pCairo, + const ::basegfx::B2DPoint& rOrigOutputPos, + bool bBufferedUpdate ) const = 0; + + protected: + ~Sprite() {} + }; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/cairo/cairo_spritecanvas.cxx b/canvas/source/cairo/cairo_spritecanvas.cxx new file mode 100644 index 0000000000..5af40370c5 --- /dev/null +++ b/canvas/source/cairo/cairo_spritecanvas.cxx @@ -0,0 +1,232 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> +#include <sal/log.hxx> + +#include <basegfx/range/b2irange.hxx> +#include <basegfx/utils/canvastools.hxx> +#include <com/sun/star/awt/XTopWindow.hpp> +#include <com/sun/star/awt/Rectangle.hpp> +#include <com/sun/star/lang/NoSupportException.hpp> +#include <osl/mutex.hxx> +#include <toolkit/helper/vclunohelper.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <cppuhelper/supportsservice.hxx> + +#include "cairo_spritecanvas.hxx" + +using namespace ::cairo; +using namespace ::com::sun::star; + +namespace cairocanvas +{ + SpriteCanvas::SpriteCanvas( const uno::Sequence< uno::Any >& aArguments, + const uno::Reference< uno::XComponentContext >& /*rxContext*/ ) : + maArguments(aArguments) + { + } + + void SpriteCanvas::initialize() + { + SAL_INFO("canvas.cairo", "CairoSpriteCanvas created " << this); + + // #i64742# Only call initialize when not in probe mode + if( !maArguments.hasElements() ) + return; + + /* maArguments: + 0: ptr to creating instance (Window or VirtualDevice) + 1: current bounds of creating instance + 2: bool, denoting always on top state for Window (always false for VirtualDevice) + 3: XWindow for creating Window (or empty for VirtualDevice) + 4: SystemGraphicsData as a streamed Any + */ + ENSURE_ARG_OR_THROW( maArguments.getLength() >= 4 && + maArguments[0].getValueTypeClass() == uno::TypeClass_HYPER && + maArguments[3].getValueTypeClass() == uno::TypeClass_INTERFACE, + "CairoSpriteCanvas::initialize: wrong number of arguments, or wrong types" ); + + awt::Rectangle aRect; + maArguments[1] >>= aRect; + + bool bIsFullscreen( false ); + maArguments[2] >>= bIsFullscreen; + + uno::Reference< awt::XWindow > xParentWindow; + maArguments[3] >>= xParentWindow; + + VclPtr<vcl::Window> pParentWindow = VCLUnoHelper::GetWindow(xParentWindow); + if( !pParentWindow ) + throw lang::NoSupportException( + "Parent window not VCL window, or canvas out-of-process!", nullptr); + + bool bHasCairo = pParentWindow->GetOutDev()->SupportsCairo(); + ENSURE_ARG_OR_THROW(bHasCairo, + "CairoSpriteCanvas::SpriteCanvas: No Cairo capability"); + + Size aPixelSize( pParentWindow->GetOutputSizePixel() ); + const ::basegfx::B2ISize aSize( aPixelSize.Width(), + aPixelSize.Height() ); + + // setup helper + maDeviceHelper.init( *pParentWindow, + *this, + aSize, + bIsFullscreen ); + + setWindow(uno::Reference<awt::XWindow2>(xParentWindow, uno::UNO_QUERY_THROW)); + + maCanvasHelper.init( maRedrawManager, + *this, + aSize ); + + maArguments.realloc(0); + } + + void SpriteCanvas::disposeThis() + { + ::osl::MutexGuard aGuard( m_aMutex ); + + // forward to parent + SpriteCanvasBaseT::disposeThis(); + } + + sal_Bool SAL_CALL SpriteCanvas::showBuffer( sal_Bool bUpdateAll ) + { + return updateScreen( bUpdateAll ); + } + + sal_Bool SAL_CALL SpriteCanvas::switchBuffer( sal_Bool bUpdateAll ) + { + return updateScreen( bUpdateAll ); + } + + sal_Bool SAL_CALL SpriteCanvas::updateScreen( sal_Bool bUpdateAll ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + + // avoid repaints on hidden window (hidden: not mapped to + // screen). Return failure, since the screen really has _not_ + // been updated (caller should try again later) + return mbIsVisible && maCanvasHelper.updateScreen( + ::basegfx::unotools::b2IRectangleFromAwtRectangle(maBounds), + bUpdateAll, + mbSurfaceDirty); + } + + OUString SAL_CALL SpriteCanvas::getServiceName( ) + { + return "com.sun.star.rendering.SpriteCanvas.Cairo"; + } + + // XServiceInfo + sal_Bool SpriteCanvas::supportsService(const OUString& sServiceName) + { + return cppu::supportsService(this, sServiceName); + + } + OUString SpriteCanvas::getImplementationName() + { + return "com.sun.star.comp.rendering.SpriteCanvas.Cairo"; + } + css::uno::Sequence< OUString > SpriteCanvas::getSupportedServiceNames() + { + return { getServiceName() }; + } + + SurfaceSharedPtr SpriteCanvas::getSurface() + { + return maDeviceHelper.getBufferSurface(); + } + + SurfaceSharedPtr SpriteCanvas::createSurface( const ::basegfx::B2ISize& rSize, int aContent ) + { + return maDeviceHelper.createSurface( rSize, aContent ); + } + + SurfaceSharedPtr SpriteCanvas::createSurface( ::Bitmap& rBitmap ) + { + BitmapSystemData aData; + if( rBitmap.GetSystemData( aData ) ) { + const Size& rSize = rBitmap.GetSizePixel(); + + return maDeviceHelper.createSurface( aData, rSize ); + } + + return SurfaceSharedPtr(); + } + + SurfaceSharedPtr SpriteCanvas::changeSurface() + { + // non-modifiable surface here + return SurfaceSharedPtr(); + } + + OutputDevice* SpriteCanvas::getOutputDevice() + { + return maDeviceHelper.getOutputDevice(); + } + + SurfaceSharedPtr const & SpriteCanvas::getBufferSurface() const + { + return maDeviceHelper.getBufferSurface(); + } + + SurfaceSharedPtr const & SpriteCanvas::getWindowSurface() const + { + return maDeviceHelper.getWindowSurface(); + } + + const ::basegfx::B2ISize& SpriteCanvas::getSizePixel() const + { + return maDeviceHelper.getSizePixel(); + } + + void SpriteCanvas::setSizePixel( const ::basegfx::B2ISize& rSize ) + { + maCanvasHelper.setSize( rSize ); + // re-set background surface, in case it needed recreation + maCanvasHelper.setSurface( maDeviceHelper.getBufferSurface(), + false ); + } + + void SpriteCanvas::flush() + { + maDeviceHelper.flush(); + } + + bool SpriteCanvas::repaint( const SurfaceSharedPtr& pSurface, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState ) + { + return maCanvasHelper.repaint( pSurface, viewState, renderState ); + } +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +com_sun_star_comp_rendering_SpriteCanvas_Cairo_get_implementation( + css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const& args) +{ + rtl::Reference<cairocanvas::SpriteCanvas> p = new cairocanvas::SpriteCanvas(args, context); + p->initialize(); + return cppu::acquire(p.get()); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/cairo/cairo_spritecanvas.hxx b/canvas/source/cairo/cairo_spritecanvas.hxx new file mode 100644 index 0000000000..7790e68904 --- /dev/null +++ b/canvas/source/cairo/cairo_spritecanvas.hxx @@ -0,0 +1,159 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/lang/XServiceName.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/awt/XWindowListener.hpp> +#include <com/sun/star/util/XUpdatable.hpp> +#include <com/sun/star/rendering/XSpriteCanvas.hpp> +#include <com/sun/star/rendering/XIntegerBitmap.hpp> +#include <com/sun/star/rendering/XGraphicDevice.hpp> +#include <com/sun/star/rendering/XBufferController.hpp> + +#include <cppuhelper/compbase.hxx> +#include <comphelper/uno3.hxx> + +#include <base/spritecanvasbase.hxx> +#include <base/spritesurface.hxx> +#include <base/disambiguationhelper.hxx> +#include <base/bufferedgraphicdevicebase.hxx> + +#include "cairo_spritedevicehelper.hxx" +#include "cairo_repainttarget.hxx" +#include "cairo_surfaceprovider.hxx" +#include "cairo_spritecanvashelper.hxx" + +namespace cairocanvas +{ + typedef ::cppu::WeakComponentImplHelper< css::rendering::XSpriteCanvas, + css::rendering::XIntegerBitmap, + css::rendering::XGraphicDevice, + css::lang::XMultiServiceFactory, + css::rendering::XBufferController, + css::awt::XWindowListener, + css::util::XUpdatable, + css::beans::XPropertySet, + css::lang::XServiceName, + css::lang::XServiceInfo > WindowGraphicDeviceBase_Base; + typedef ::canvas::BufferedGraphicDeviceBase< ::canvas::DisambiguationHelper< WindowGraphicDeviceBase_Base >, + SpriteDeviceHelper, + ::osl::MutexGuard, + ::cppu::OWeakObject > SpriteCanvasBase_Base; + /** Mixin SpriteSurface + + Have to mixin the SpriteSurface before deriving from + ::canvas::SpriteCanvasBase, as this template should already + implement some of those interface methods. + + The reason why this appears kinda convoluted is the fact that + we cannot specify non-IDL types as WeakComponentImplHelper + template args, and furthermore, don't want to derive + ::canvas::SpriteCanvasBase directly from + ::canvas::SpriteSurface (because derivees of + ::canvas::SpriteCanvasBase have to explicitly forward the + XInterface methods (e.g. via DECLARE_UNO3_AGG_DEFAULTS) + anyway). Basically, ::canvas::CanvasCustomSpriteBase should + remain a base class that provides implementation, not to + enforce any specific interface on its derivees. + */ + class SpriteCanvasBaseSpriteSurface_Base : public SpriteCanvasBase_Base, + public ::canvas::SpriteSurface, + public SurfaceProvider + { + }; + + typedef ::canvas::SpriteCanvasBase< SpriteCanvasBaseSpriteSurface_Base, + SpriteCanvasHelper, + ::osl::MutexGuard, + ::cppu::OWeakObject > SpriteCanvasBaseT; + + /** Product of this component's factory. + + The SpriteCanvas object combines the actual Window canvas with + the XGraphicDevice interface. This is because there's a + one-to-one relation between them, anyway, since each window + can have exactly one canvas and one associated + XGraphicDevice. And to avoid messing around with circular + references, this is implemented as one single object. + */ + class SpriteCanvas : public SpriteCanvasBaseT, + public RepaintTarget + { + public: + SpriteCanvas( const css::uno::Sequence< css::uno::Any >& aArguments, + const css::uno::Reference< css::uno::XComponentContext >& rxContext ); + + void initialize(); + + /// Dispose all internal references + virtual void disposeThis() override; + + // Forwarding the XComponent implementation to the + // cppu::ImplHelper templated base + // Classname Base doing refcounting Base implementing the XComponent interface + // | | | + // V V V + DECLARE_UNO3_XCOMPONENT_AGG_DEFAULTS( SpriteCanvas, WindowGraphicDeviceBase_Base, ::cppu::WeakComponentImplHelperBase ) + + // XBufferController (partial) + virtual sal_Bool SAL_CALL showBuffer( sal_Bool bUpdateAll ) override; + virtual sal_Bool SAL_CALL switchBuffer( sal_Bool bUpdateAll ) override; + + // XSpriteCanvas (partial) + virtual sal_Bool SAL_CALL updateScreen( sal_Bool bUpdateAll ) override; + + // XServiceName + virtual OUString SAL_CALL getServiceName( ) override; + + // XServiceInfo + virtual sal_Bool SAL_CALL supportsService(const OUString& sServiceName) override; + virtual OUString SAL_CALL getImplementationName() override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override; + + // SurfaceProvider + virtual ::cairo::SurfaceSharedPtr getSurface() override; + virtual ::cairo::SurfaceSharedPtr createSurface( const ::basegfx::B2ISize& rSize, int aContent ) override; + virtual ::cairo::SurfaceSharedPtr createSurface( ::Bitmap& rBitmap ) override; + virtual ::cairo::SurfaceSharedPtr changeSurface() override; + virtual OutputDevice* getOutputDevice() override; + + // RepaintTarget + virtual bool repaint( const ::cairo::SurfaceSharedPtr& pSurface, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState ) override; + + ::cairo::SurfaceSharedPtr const & getWindowSurface() const; + ::cairo::SurfaceSharedPtr const & getBufferSurface() const; + + const ::basegfx::B2ISize& getSizePixel() const; + void setSizePixel( const ::basegfx::B2ISize& rSize ); + void flush(); + + private: + css::uno::Sequence< css::uno::Any > maArguments; + }; + + typedef ::rtl::Reference< SpriteCanvas > SpriteCanvasRef; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/cairo/cairo_spritecanvashelper.cxx b/canvas/source/cairo/cairo_spritecanvashelper.cxx new file mode 100644 index 0000000000..3fcfd734ab --- /dev/null +++ b/canvas/source/cairo/cairo_spritecanvashelper.cxx @@ -0,0 +1,518 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> +#include <sal/log.hxx> + +#include <boost/cast.hpp> + +#include <basegfx/range/b2irange.hxx> +#include <comphelper/diagnose_ex.hxx> + +#include <canvas/canvastools.hxx> + +#include <cairo.h> + +#include "cairo_canvascustomsprite.hxx" +#include "cairo_spritecanvashelper.hxx" + +using namespace ::cairo; +using namespace ::com::sun::star; + +namespace cairocanvas +{ + namespace + { + /** Sprite redraw at original position + + Used to repaint the whole canvas (background and all + sprites) + */ + void spriteRedraw( const CairoSharedPtr& pCairo, + const ::canvas::Sprite::Reference& rSprite ) + { + // downcast to derived cairocanvas::Sprite interface, which + // provides the actual redraw methods. + ::boost::polymorphic_downcast< Sprite* >(rSprite.get())->redraw( pCairo, true); + } + + void repaintBackground( const CairoSharedPtr& pCairo, + const SurfaceSharedPtr& pBackgroundSurface, + const ::basegfx::B2DRange& rArea ) + { + cairo_save( pCairo.get() ); + cairo_rectangle( pCairo.get(), ceil( rArea.getMinX() ), ceil( rArea.getMinY() ), + floor( rArea.getWidth() ), floor( rArea.getHeight() ) ); + cairo_clip( pCairo.get() ); + cairo_set_source_surface( pCairo.get(), pBackgroundSurface->getCairoSurface().get(), 0, 0 ); + cairo_set_operator( pCairo.get(), CAIRO_OPERATOR_SOURCE ); + cairo_paint( pCairo.get() ); + cairo_restore( pCairo.get() ); + } + + void opaqueUpdateSpriteArea( const ::canvas::Sprite::Reference& rSprite, + const CairoSharedPtr& pCairo, + const ::basegfx::B2IRange& rArea ) + { + // clip output to actual update region (otherwise a) + // wouldn't save much render time, and b) will clutter + // scrolled sprite content outside this area) + cairo_save( pCairo.get() ); + cairo_rectangle( pCairo.get(), rArea.getMinX(), rArea.getMinY(), + sal::static_int_cast<sal_Int32>(rArea.getWidth()), + sal::static_int_cast<sal_Int32>(rArea.getHeight()) ); + cairo_clip( pCairo.get() ); + + // repaint affected sprite directly to output device (at + // the actual screen output position) + // rendering directly to device buffer + ::boost::polymorphic_downcast< Sprite* >( rSprite.get() )->redraw( pCairo, false ); + + cairo_restore( pCairo.get() ); + } + } + + SpriteCanvasHelper::SpriteCanvasHelper() : + mpRedrawManager( nullptr ), + mpOwningSpriteCanvas( nullptr ), + mbCompositingSurfaceDirty(true) + { + } + + void SpriteCanvasHelper::init( ::canvas::SpriteRedrawManager& rManager, + SpriteCanvas& rDevice, + const ::basegfx::B2ISize& rSize ) + { + mpRedrawManager = &rManager; + mpOwningSpriteCanvas = &rDevice; + + CanvasHelper::init( rSize, rDevice, &rDevice ); + } + + void SpriteCanvasHelper::disposing() + { + mpCompositingSurface.reset(); + mpOwningSpriteCanvas = nullptr; + mpRedrawManager = nullptr; + + // forward to base + CanvasHelper::disposing(); + } + + uno::Reference< rendering::XAnimatedSprite > SpriteCanvasHelper::createSpriteFromAnimation( + const uno::Reference< rendering::XAnimation >& ) + { + return uno::Reference< rendering::XAnimatedSprite >(); + } + + uno::Reference< rendering::XAnimatedSprite > SpriteCanvasHelper::createSpriteFromBitmaps( + const uno::Sequence< uno::Reference< rendering::XBitmap > >& /*animationBitmaps*/, + sal_Int8 /*interpolationMode*/ ) + { + return uno::Reference< rendering::XAnimatedSprite >(); + } + + uno::Reference< rendering::XCustomSprite > SpriteCanvasHelper::createCustomSprite( const geometry::RealSize2D& spriteSize ) + { + if( !mpRedrawManager ) + return uno::Reference< rendering::XCustomSprite >(); // we're disposed + + return uno::Reference< rendering::XCustomSprite >( + new CanvasCustomSprite( spriteSize, + mpOwningSpriteCanvas ) ); + } + + uno::Reference< rendering::XSprite > SpriteCanvasHelper::createClonedSprite( + const uno::Reference< rendering::XSprite >& ) + { + return uno::Reference< rendering::XSprite >(); + } + + bool SpriteCanvasHelper::updateScreen( const ::basegfx::B2IRange& /*rCurrArea*/, + bool bUpdateAll, + bool& io_bSurfaceDirty ) + { + if( !mpRedrawManager || + !mpOwningSpriteCanvas || + !mpOwningSpriteCanvas->getWindowSurface() || + !mpOwningSpriteCanvas->getBufferSurface() ) + { + return false; // disposed, or otherwise dysfunctional + } + + SAL_INFO("canvas.cairo", "SpriteCanvasHelper::updateScreen called"); + + const ::basegfx::B2ISize& rSize = mpOwningSpriteCanvas->getSizePixel(); + + // force compositing surface to be available before using it + // inside forEachSpriteArea + SurfaceSharedPtr pCompositingSurface = getCompositingSurface(rSize); + SurfaceSharedPtr pWindowSurface = mpOwningSpriteCanvas->getWindowSurface(); + CairoSharedPtr pCompositingCairo = pCompositingSurface->getCairo(); + CairoSharedPtr pWindowCairo = pWindowSurface->getCairo(); + + // TODO(P1): Might be worthwhile to track areas of background + // changes, too. + if( !bUpdateAll && !io_bSurfaceDirty && !mbCompositingSurfaceDirty ) + { + // background has not changed, so we're free to optimize + // repaint to areas where a sprite has changed + + // process each independent area of overlapping sprites + // separately. + mpRedrawManager->forEachSpriteArea( *this ); + } + else + { + SAL_INFO("canvas.cairo", "SpriteCanvasHelper::updateScreen update ALL"); + + // background has changed, so we currently have no choice + // but repaint everything (or caller requested that) + + cairo_rectangle( pCompositingCairo.get(), 0, 0, rSize.getWidth(), rSize.getHeight() ); + cairo_clip( pCompositingCairo.get() ); + cairo_save( pCompositingCairo.get() ); + cairo_set_source_surface( pCompositingCairo.get(), + mpOwningSpriteCanvas->getBufferSurface()->getCairoSurface().get(), + 0, 0 ); + cairo_set_operator( pCompositingCairo.get(), CAIRO_OPERATOR_SOURCE ); + cairo_paint( pCompositingCairo.get() ); + cairo_restore( pCompositingCairo.get() ); + + // repaint all active sprites on top of background into + // VDev. + mpRedrawManager->forEachSprite( + [&pCompositingCairo]( const Sprite::Reference rSprite ) + { spriteRedraw( pCompositingCairo, rSprite ); } + ); + + // flush to screen + cairo_rectangle( pWindowCairo.get(), 0, 0, rSize.getWidth(), rSize.getHeight() ); + cairo_clip( pWindowCairo.get() ); + cairo_set_source_surface( pWindowCairo.get(), + pCompositingSurface->getCairoSurface().get(), + 0, 0 ); + cairo_set_operator( pWindowCairo.get(), CAIRO_OPERATOR_SOURCE ); + cairo_paint( pWindowCairo.get() ); + } + + // change record vector must be cleared, for the next turn of + // rendering and sprite changing + mpRedrawManager->clearChangeRecords(); + + mbCompositingSurfaceDirty = false; + io_bSurfaceDirty = false; + + // commit to screen + mpOwningSpriteCanvas->flush(); + + return true; + } + + void SpriteCanvasHelper::backgroundPaint( const ::basegfx::B2DRange& rUpdateRect ) + { + if( mpOwningSpriteCanvas && mpCompositingSurface ) + repaintBackground( mpCompositingSurface->getCairo(), + mpOwningSpriteCanvas->getBufferSurface(), + rUpdateRect ); + } + + void SpriteCanvasHelper::scrollUpdate( const ::basegfx::B2DRange& rMoveStart, + const ::basegfx::B2DRange& rMoveEnd, + const ::canvas::SpriteRedrawManager::UpdateArea& rUpdateArea ) + { + ENSURE_OR_THROW( mpOwningSpriteCanvas && + mpOwningSpriteCanvas->getBufferSurface(), + "SpriteCanvasHelper::scrollUpdate(): NULL device pointer " ); + + SAL_INFO("canvas.cairo", "SpriteCanvasHelper::scrollUpdate called"); + + const ::basegfx::B2ISize& rSize = mpOwningSpriteCanvas->getSizePixel(); + const ::basegfx::B2IRange aOutputBounds( 0,0, + rSize.getWidth(), + rSize.getHeight() ); + + SurfaceSharedPtr pCompositingSurface = getCompositingSurface(rSize); + SurfaceSharedPtr pWindowSurface = mpOwningSpriteCanvas->getWindowSurface(); + CairoSharedPtr pCompositingCairo = pCompositingSurface->getCairo(); + CairoSharedPtr pWindowCairo = pWindowSurface->getCairo(); + + // round rectangles to integer pixel. Note: have to be + // extremely careful here, to avoid off-by-one errors for + // the destination area: otherwise, the next scroll update + // would copy pixel that are not supposed to be part of + // the sprite. + ::basegfx::B2IRange aSourceRect( + ::canvas::tools::spritePixelAreaFromB2DRange( rMoveStart ) ); + const ::basegfx::B2IRange& rDestRect( + ::canvas::tools::spritePixelAreaFromB2DRange( rMoveEnd ) ); + ::basegfx::B2IPoint aDestPos( rDestRect.getMinimum() ); + + std::vector< ::basegfx::B2IRange > aUnscrollableAreas; + + // TODO(E3): This is plain buggy (but copies the behaviour of + // the old Impress slideshow) - the scrolled area might + // actually lie _below_ another window! + + // clip to output bounds (cannot properly scroll stuff + // _outside_ our screen area) + if( !::canvas::tools::clipScrollArea( aSourceRect, + aDestPos, + aUnscrollableAreas, + aOutputBounds ) ) + { + // fully clipped scroll area: cannot simply scroll + // then. Perform normal opaque update (can use that, since + // one of the preconditions for scrollable update is + // opaque sprite content) + + // repaint all affected sprites directly to output device + for( const auto& rComponent : rUpdateArea.maComponentList ) + { + const ::canvas::Sprite::Reference& rSprite( rComponent.second.getSprite() ); + if( rSprite.is() ) + ::boost::polymorphic_downcast< Sprite* >( rSprite.get() )->redraw( + pCompositingCairo, true ); + } + } + else + { + const ::basegfx::B2IVector aSourceUpperLeftPos( aSourceRect.getMinimum() ); + + // clip dest area (which must be inside rDestBounds) + ::basegfx::B2IRange aDestRect( rDestRect ); + aDestRect.intersect( aOutputBounds ); + + ::basegfx::B2ISize aScrollSize( aDestRect.getWidth(), aDestRect.getHeight() ); + SurfaceSharedPtr pScrollSurface( getTemporarySurface() ); + CairoSharedPtr pScrollCairo( pScrollSurface->getCairo() ); + + cairo_save( pScrollCairo.get() ); + // scroll the current content of the compositing surface (and, + // thus, of the window) in temp. surface + cairo_set_source_surface( pScrollCairo.get(), + pCompositingSurface->getCairoSurface().get(), + aDestPos.getX() - aSourceUpperLeftPos.getX(), + aDestPos.getY() - aSourceUpperLeftPos.getY() ); + cairo_rectangle( pScrollCairo.get(), + aDestPos.getX(), aDestPos.getY(), + aScrollSize.getWidth(), aScrollSize.getHeight() ); + cairo_clip( pScrollCairo.get() ); + cairo_set_operator( pScrollCairo.get(), CAIRO_OPERATOR_SOURCE ); + cairo_paint( pScrollCairo.get() ); + cairo_restore( pScrollCairo.get() ); + + cairo_save( pCompositingCairo.get() ); + // copy the scrolled area back onto the compositing surface + cairo_set_source_surface( pCompositingCairo.get(), + pScrollSurface->getCairoSurface().get(), + 0, 0 ); + cairo_rectangle( pCompositingCairo.get(), + aDestPos.getX(), aDestPos.getY(), + aScrollSize.getWidth(), aScrollSize.getHeight() ); + cairo_clip( pCompositingCairo.get() ); + cairo_set_operator( pCompositingCairo.get(), CAIRO_OPERATOR_SOURCE ); + cairo_paint( pCompositingCairo.get() ); + cairo_restore( pCompositingCairo.get() ); + + const ::canvas::SpriteRedrawManager::SpriteConnectedRanges::ComponentListType::const_iterator + aFirst( rUpdateArea.maComponentList.begin() ); + ::canvas::SpriteRedrawManager::SpriteConnectedRanges::ComponentListType::const_iterator + aSecond( aFirst ); + ++aSecond; + + ENSURE_OR_THROW( aFirst->second.getSprite().is(), + "VCLCanvas::scrollUpdate(): no sprite" ); + + // repaint uncovered areas from sprite. Need to actually + // clip here, since we're only repainting _parts_ of the + // sprite + for( const auto& rArea : aUnscrollableAreas ) + opaqueUpdateSpriteArea( aFirst->second.getSprite(), + pCompositingCairo, rArea ); + } + + // repaint uncovered areas from backbuffer - take the + // _rounded_ rectangles from above, to have the update + // consistent with the scroll above. + std::vector< ::basegfx::B2DRange > aUncoveredAreas; + ::basegfx::computeSetDifference( aUncoveredAreas, + rUpdateArea.maTotalBounds, + ::basegfx::B2DRange( rDestRect ) ); + for( const auto& rArea : aUncoveredAreas ) + repaintBackground( pCompositingCairo, + mpOwningSpriteCanvas->getBufferSurface(), rArea ); + + cairo_rectangle( pWindowCairo.get(), 0, 0, rSize.getWidth(), rSize.getHeight() ); + cairo_clip( pWindowCairo.get() ); + cairo_set_source_surface( pWindowCairo.get(), + pCompositingSurface->getCairoSurface().get(), + 0, 0 ); + cairo_set_operator( pWindowCairo.get(), CAIRO_OPERATOR_SOURCE ); + cairo_paint( pWindowCairo.get() ); + } + + void SpriteCanvasHelper::opaqueUpdate( const ::basegfx::B2DRange& rTotalArea, + const std::vector< ::canvas::Sprite::Reference >& rSortedUpdateSprites ) + { + ENSURE_OR_THROW( mpOwningSpriteCanvas && + mpOwningSpriteCanvas->getBufferSurface(), + "SpriteCanvasHelper::opaqueUpdate(): NULL device pointer " ); + + SAL_INFO("canvas.cairo", "SpriteCanvasHelper::opaqueUpdate called"); + + const ::basegfx::B2ISize& rDeviceSize = mpOwningSpriteCanvas->getSizePixel(); + + SurfaceSharedPtr pCompositingSurface = getCompositingSurface(rDeviceSize); + SurfaceSharedPtr pWindowSurface = mpOwningSpriteCanvas->getWindowSurface(); + CairoSharedPtr pCompositingCairo = pCompositingSurface->getCairo(); + CairoSharedPtr pWindowCairo = pWindowSurface->getCairo(); + + cairo_rectangle( pCompositingCairo.get(), 0, 0, rDeviceSize.getWidth(), rDeviceSize.getHeight() ); + cairo_clip( pCompositingCairo.get() ); + + ::basegfx::B2DVector aPos( ceil( rTotalArea.getMinX() ), ceil( rTotalArea.getMinY() ) ); + ::basegfx::B2DVector aSize( floor( rTotalArea.getMaxX() - aPos.getX() ), floor( rTotalArea.getMaxY() - aPos.getY() ) ); + + cairo_rectangle( pCompositingCairo.get(), aPos.getX(), aPos.getY(), aSize.getX(), aSize.getY() ); + cairo_clip( pCompositingCairo.get() ); + + // repaint all affected sprites directly to output device + for( const auto& rSprite : rSortedUpdateSprites ) + { + if( rSprite.is() ) + ::boost::polymorphic_downcast< Sprite* >( rSprite.get() )->redraw( + pCompositingCairo, false ); + } + + // flush to screen + cairo_rectangle( pWindowCairo.get(), 0, 0, rDeviceSize.getWidth(), rDeviceSize.getHeight() ); + cairo_clip( pWindowCairo.get() ); + cairo_rectangle( pWindowCairo.get(), aPos.getX(), aPos.getY(), aSize.getX(), aSize.getY() ); + cairo_clip( pWindowCairo.get() ); + cairo_set_source_surface( pWindowCairo.get(), + pCompositingSurface->getCairoSurface().get(), + 0, 0 ); + cairo_set_operator( pWindowCairo.get(), CAIRO_OPERATOR_SOURCE ); + cairo_paint( pWindowCairo.get() ); + } + + void SpriteCanvasHelper::genericUpdate( const ::basegfx::B2DRange& rRequestedArea, + const std::vector< ::canvas::Sprite::Reference >& rSortedUpdateSprites ) + { + // TODO + SAL_INFO("canvas.cairo", "SpriteCanvasHelper::genericUpdate called"); + + ENSURE_OR_THROW( mpOwningSpriteCanvas && + mpOwningSpriteCanvas->getBufferSurface(), + "SpriteCanvasHelper::genericUpdate(): NULL device pointer " ); + + // limit size of update VDev to target outdev's size + const ::basegfx::B2ISize& rSize = mpOwningSpriteCanvas->getSizePixel(); + + SurfaceSharedPtr pCompositingSurface = getCompositingSurface(rSize); + SurfaceSharedPtr pWindowSurface = mpOwningSpriteCanvas->getWindowSurface(); + CairoSharedPtr pCompositingCairo = pCompositingSurface->getCairo(); + CairoSharedPtr pWindowCairo = pWindowSurface->getCairo(); + + // round output position towards zero. Don't want to truncate + // a fraction of a sprite pixel... Clip position at origin, + // otherwise, truncation of size below might leave visible + // areas uncovered by VDev. + const Point aOutputPosition( + std::max( sal_Int32( 0 ), + static_cast< sal_Int32 >(rRequestedArea.getMinX()) ), + std::max( sal_Int32( 0 ), + static_cast< sal_Int32 >(rRequestedArea.getMinY()) ) ); + // round output size towards +infty. Don't want to truncate a + // fraction of a sprite pixel... Limit size of VDev to output + // device's area. + const Size aOutputSize( + std::min( rSize.getWidth(), + ::canvas::tools::roundUp( rRequestedArea.getMaxX() - aOutputPosition.X()) ), + std::min( rSize.getHeight(), + ::canvas::tools::roundUp( rRequestedArea.getMaxY() - aOutputPosition.Y()) ) ); + + cairo_rectangle( pCompositingCairo.get(), aOutputPosition.X(), aOutputPosition.Y(), aOutputSize.Width(), aOutputSize.Height() ); + cairo_clip( pCompositingCairo.get() ); + + // paint background + cairo_save( pCompositingCairo.get() ); + cairo_set_source_surface( pCompositingCairo.get(), + mpOwningSpriteCanvas->getBufferSurface()->getCairoSurface().get(), + 0, 0 ); + cairo_set_operator( pCompositingCairo.get(), CAIRO_OPERATOR_SOURCE ); + cairo_paint( pCompositingCairo.get() ); + cairo_restore( pCompositingCairo.get() ); + + // repaint all affected sprites on top of background into + // VDev. + for( const auto& rSprite : rSortedUpdateSprites ) + { + if( rSprite.is() ) + ::boost::polymorphic_downcast< Sprite* >( rSprite.get() )->redraw( + pCompositingCairo, true ); + } + + // flush to screen + cairo_rectangle( pWindowCairo.get(), aOutputPosition.X(), aOutputPosition.Y(), aOutputSize.Width(), aOutputSize.Height() ); + cairo_clip( pWindowCairo.get() ); + cairo_set_source_surface( pWindowCairo.get(), + pCompositingSurface->getCairoSurface().get(), + 0, 0 ); + cairo_set_operator( pWindowCairo.get(), CAIRO_OPERATOR_SOURCE ); + cairo_paint( pWindowCairo.get() ); + } + + ::cairo::SurfaceSharedPtr const & SpriteCanvasHelper::getCompositingSurface( const ::basegfx::B2ISize& rNeededSize ) + { + if( rNeededSize.getWidth() > maCompositingSurfaceSize.getWidth() || + rNeededSize.getHeight() > maCompositingSurfaceSize.getHeight() ) + { + // need to give buffer more size + mpCompositingSurface.reset(); + } + + if( !mpCompositingSurface ) + { + mpCompositingSurface = createSurface( rNeededSize ); + maCompositingSurfaceSize = rNeededSize; + mbCompositingSurfaceDirty = true; + mpTemporarySurface.reset(); + } + + return mpCompositingSurface; + } + + ::cairo::SurfaceSharedPtr const & SpriteCanvasHelper::getTemporarySurface() + { + if ( !mpTemporarySurface ) + mpTemporarySurface = createSurface( maCompositingSurfaceSize ); + return mpTemporarySurface; + } + + ::cairo::SurfaceSharedPtr SpriteCanvasHelper::createSurface( const ::basegfx::B2ISize& rNeededSize ) const + { + return mpOwningSpriteCanvas->getWindowSurface()->getSimilar( + CAIRO_CONTENT_COLOR, + rNeededSize.getWidth(), rNeededSize.getHeight() ); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/cairo/cairo_spritecanvashelper.hxx b/canvas/source/cairo/cairo_spritecanvashelper.hxx new file mode 100644 index 0000000000..2b11f1bcf0 --- /dev/null +++ b/canvas/source/cairo/cairo_spritecanvashelper.hxx @@ -0,0 +1,140 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/rendering/XAnimatedSprite.hpp> +#include <com/sun/star/rendering/XAnimation.hpp> +#include <com/sun/star/rendering/XCustomSprite.hpp> + +#include <spriteredrawmanager.hxx> + +#include <vcl/cairo.hxx> +#include "cairo_canvashelper.hxx" + +namespace basegfx +{ + class B2IRange; +} + +namespace cairocanvas +{ + class SpriteCanvas; + + class SpriteCanvasHelper : public CanvasHelper + { + public: + SpriteCanvasHelper(); + + void init( ::canvas::SpriteRedrawManager& rManager, + SpriteCanvas& rOwningSpriteCanvas, + const ::basegfx::B2ISize& rSize ); + + /// Dispose all internal references + void disposing(); + + // XSpriteCanvas + css::uno::Reference< css::rendering::XAnimatedSprite > createSpriteFromAnimation( + const css::uno::Reference< css::rendering::XAnimation >& animation ); + + css::uno::Reference< css::rendering::XAnimatedSprite > createSpriteFromBitmaps( + const css::uno::Sequence< + css::uno::Reference< + css::rendering::XBitmap > >& animationBitmaps, + sal_Int8 interpolationMode ); + + css::uno::Reference< css::rendering::XCustomSprite > createCustomSprite( + const css::geometry::RealSize2D& spriteSize ); + + css::uno::Reference< css::rendering::XSprite > createClonedSprite( + const css::uno::Reference< css::rendering::XSprite >& original ); + + /** Actually perform the screen update + + @param rCurrArea + Current window area in absolute screen coordinates + + @param bUpdateAll + sal_True, if everything must be updated, not only changed + sprites + + @param io_bSurfaceDirty + In/out parameter, whether backbuffer surface is dirty (if + yes, we're performing a full update, anyway) + */ + bool updateScreen( const ::basegfx::B2IRange& rCurrArea, + bool bUpdateAll, + bool& io_bSurfaceDirty ); + + + // SpriteRedrawManager functor calls + + + /** Gets called for simple background repaints + */ + void backgroundPaint( const ::basegfx::B2DRange& rUpdateRect ); + + /** Gets called when area can be handled by scrolling. + + Called method must copy screen content from rMoveStart to + rMoveEnd, and restore the background in the uncovered + areas. + + @param rMoveStart + Source rect of the scroll + + @param rMoveEnd + Dest rect of the scroll + + @param rUpdateArea + All info necessary, should rMoveStart be partially or + fully outside the outdev + */ + void scrollUpdate( const ::basegfx::B2DRange& rMoveStart, + const ::basegfx::B2DRange& rMoveEnd, + const ::canvas::SpriteRedrawManager::UpdateArea& rUpdateArea ); + + void opaqueUpdate( const ::basegfx::B2DRange& rTotalArea, + const std::vector< ::canvas::Sprite::Reference >& rSortedUpdateSprites ); + + void genericUpdate( const ::basegfx::B2DRange& rTotalArea, + const std::vector< ::canvas::Sprite::Reference >& rSortedUpdateSprites ); + + private: + ::cairo::SurfaceSharedPtr const & getCompositingSurface( const ::basegfx::B2ISize& rNeededSize ); + ::cairo::SurfaceSharedPtr const & getTemporarySurface(); + ::cairo::SurfaceSharedPtr createSurface( const ::basegfx::B2ISize& rNeededSize ) const; + + /// Set from the SpriteCanvas: instance coordinating sprite redraw + ::canvas::SpriteRedrawManager* mpRedrawManager; + + /// Set from the init method. used to generate sprites + SpriteCanvas* mpOwningSpriteCanvas; + + /// a surface used to composite the frontbuffer image + ::cairo::SurfaceSharedPtr mpCompositingSurface; + ::basegfx::B2ISize maCompositingSurfaceSize; + bool mbCompositingSurfaceDirty; + /// a temporary surface that is guaranteed to be the same size + //as the compositing surface + ::cairo::SurfaceSharedPtr mpTemporarySurface; + }; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/cairo/cairo_spritedevicehelper.cxx b/canvas/source/cairo/cairo_spritedevicehelper.cxx new file mode 100644 index 0000000000..69a057c991 --- /dev/null +++ b/canvas/source/cairo/cairo_spritedevicehelper.cxx @@ -0,0 +1,153 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> +#include <sal/log.hxx> + +#include <vcl/cairo.hxx> + +#include <cairo.h> + +#include "cairo_devicehelper.hxx" +#include "cairo_spritecanvas.hxx" + +using namespace ::cairo; +using namespace ::com::sun::star; + +namespace cairocanvas +{ + + SpriteDeviceHelper::SpriteDeviceHelper() : + mpSpriteCanvas( nullptr ), + mbFullScreen( false ) + {} + + void SpriteDeviceHelper::init( vcl::Window& rOutputWindow, + SpriteCanvas& rSpriteCanvas, + const ::basegfx::B2ISize& rSize, + bool bFullscreen ) + { + DeviceHelper::init(rSpriteCanvas, + *rOutputWindow.GetOutDev()); + + mpSpriteCanvas = &rSpriteCanvas; + mbFullScreen = bFullscreen; + + setSize( rSize ); + } + + void SpriteDeviceHelper::disposing() + { + // release all references + mpBufferSurface.reset(); + mpSpriteCanvas = nullptr; + } + + bool SpriteDeviceHelper::showBuffer( bool, bool ) + { + SAL_WARN("canvas.cairo", "showBuffer Not supposed to be called, handled by SpriteCanvas"); + return false; + } + + bool SpriteDeviceHelper::switchBuffer( bool, bool ) + { + SAL_WARN("canvas.cairo", "showBuffer Not supposed to be called, handled by SpriteCanvas"); + return false; + } + + uno::Any SpriteDeviceHelper::isAccelerated() const + { + return css::uno::Any(true); + } + + uno::Any SpriteDeviceHelper::getDeviceHandle() const + { + return DeviceHelper::getDeviceHandle(); + } + + uno::Any SpriteDeviceHelper::getSurfaceHandle() const + { + return DeviceHelper::getSurfaceHandle(); + } + + void SpriteDeviceHelper::setSize( const ::basegfx::B2ISize& rSize ) + { + SAL_INFO( + "canvas.cairo", + "device size " << rSize.getWidth() << " x " << rSize.getHeight()); + + if( !mpSpriteCanvas ) + return; // disposed + + DeviceHelper::setSize(rSize); + + if( mpBufferSurface && maSize != rSize ) + mpBufferSurface.reset(); + if( !mpBufferSurface ) + mpBufferSurface = getWindowSurface()->getSimilar( + CAIRO_CONTENT_COLOR, + rSize.getWidth(), rSize.getHeight() ); + + if( maSize != rSize ) + maSize = rSize; + + mpSpriteCanvas->setSizePixel( maSize ); + } + + + void SpriteDeviceHelper::notifySizeUpdate( const awt::Rectangle& rBounds ) + { + setSize( ::basegfx::B2ISize(rBounds.Width, rBounds.Height) ); + } + + SurfaceSharedPtr const & SpriteDeviceHelper::getWindowSurface() const + { + return DeviceHelper::getSurface(); + } + + SurfaceSharedPtr SpriteDeviceHelper::createSurface( const ::basegfx::B2ISize& rSize, int aContent ) + { + if( mpBufferSurface ) + return mpBufferSurface->getSimilar( aContent, rSize.getWidth(), rSize.getHeight() ); + + return SurfaceSharedPtr(); + } + + SurfaceSharedPtr SpriteDeviceHelper::createSurface( BitmapSystemData const & rData, const Size& rSize ) + { + OutputDevice *pDevice = getOutputDevice(); + if (pDevice) + return pDevice->CreateBitmapSurface(rData, rSize); + return SurfaceSharedPtr(); + } + + /** SpriteDeviceHelper::flush Flush the platform native window + * + * Flushes the window by using the internally stored mpSysData. + * + **/ + void SpriteDeviceHelper::flush() + { + SurfaceSharedPtr pWinSurface=getWindowSurface(); + if( pWinSurface ) + pWinSurface->flush(); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/cairo/cairo_spritedevicehelper.hxx b/canvas/source/cairo/cairo_spritedevicehelper.hxx new file mode 100644 index 0000000000..63e9d13039 --- /dev/null +++ b/canvas/source/cairo/cairo_spritedevicehelper.hxx @@ -0,0 +1,77 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/awt/Rectangle.hpp> + +#include <vcl/window.hxx> +#include <vcl/cairo.hxx> + +#include "cairo_devicehelper.hxx" + +/* Definition of DeviceHelper class */ + +namespace cairocanvas +{ + class SpriteCanvas; + + class SpriteDeviceHelper : public DeviceHelper + { + public: + SpriteDeviceHelper(); + + void init( vcl::Window& rOutputWindow, + SpriteCanvas& rSpriteCanvas, + const ::basegfx::B2ISize& rSize, + bool bFullscreen ); + + /// Dispose all internal references + void disposing(); + + // XWindowGraphicDevice + bool showBuffer( bool, bool ); + bool switchBuffer( bool, bool bUpdateAll ); + + css::uno::Any isAccelerated() const; + css::uno::Any getDeviceHandle() const; + css::uno::Any getSurfaceHandle() const; + + void notifySizeUpdate( const css::awt::Rectangle& rBounds ); + void setSize( const ::basegfx::B2ISize& rSize ); + + const ::cairo::SurfaceSharedPtr& getBufferSurface() const { return mpBufferSurface; } + ::cairo::SurfaceSharedPtr const & getWindowSurface() const; + ::cairo::SurfaceSharedPtr createSurface( const ::basegfx::B2ISize& rSize, int aContent ); + ::cairo::SurfaceSharedPtr createSurface( BitmapSystemData const & rData, const Size& rSize ); + const ::basegfx::B2ISize& getSizePixel() const { return maSize; } + void flush(); + + private: + /// Pointer to sprite canvas (owner of this helper), needed to create bitmaps + SpriteCanvas* mpSpriteCanvas; + + ::cairo::SurfaceSharedPtr mpBufferSurface; + + ::basegfx::B2ISize maSize; + bool mbFullScreen; + }; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/cairo/cairo_spritehelper.cxx b/canvas/source/cairo/cairo_spritehelper.cxx new file mode 100644 index 0000000000..4e53626198 --- /dev/null +++ b/canvas/source/cairo/cairo_spritehelper.cxx @@ -0,0 +1,195 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> +#include <sal/log.hxx> + +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/numeric/ftools.hxx> +#include <basegfx/point/b2dpoint.hxx> +#include <basegfx/utils/canvastools.hxx> +#include <rtl/math.hxx> +#include <comphelper/diagnose_ex.hxx> + +#include <cairo.h> +#include <pixman.h> + +#include "cairo_spritehelper.hxx" + +using namespace ::cairo; +using namespace ::com::sun::star; + +namespace cairocanvas +{ + SpriteHelper::SpriteHelper() : + mbTextureDirty(true) + {} + + void SpriteHelper::init( const geometry::RealSize2D& rSpriteSize, + const SpriteCanvasRef& rSpriteCanvas ) + { + ENSURE_OR_THROW( rSpriteCanvas, + "SpriteHelper::init(): Invalid device, sprite canvas or surface" ); + + mpSpriteCanvas = rSpriteCanvas; + mbTextureDirty = true; + + // also init base class + CanvasCustomSpriteHelper::init( rSpriteSize, rSpriteCanvas ); + } + + void SpriteHelper::setSurface( const SurfaceSharedPtr& pBufferSurface ) + { + mpBufferSurface = pBufferSurface; + } + + void SpriteHelper::disposing() + { + mpBufferSurface.reset(); + mpSpriteCanvas.clear(); + + // forward to parent + CanvasCustomSpriteHelper::disposing(); + } + + void SpriteHelper::redraw( const CairoSharedPtr& pCairo, + const ::basegfx::B2DPoint& rPos, + bool& /*io_bSurfacesDirty*/, + bool /*bBufferedUpdate*/ ) const + { +#ifdef CAIRO_CANVAS_PERF_TRACE + struct timespec aTimer; + mxDevice->startPerfTrace( &aTimer ); +#endif + + const double fAlpha( getAlpha() ); + const ::basegfx::B2DHomMatrix aTransform( getTransformation() ); + + if( !isActive() || ::basegfx::fTools::equalZero( fAlpha ) ) + return; + + SAL_INFO( "canvas.cairo", "CanvasCustomSprite::redraw called"); + if( !pCairo ) + return; + + basegfx::B2DVector aSize = getSizePixel(); + cairo_save( pCairo.get() ); + + double fX, fY; + + fX = rPos.getX(); + fY = rPos.getY(); + + if( !aTransform.isIdentity() ) + { + cairo_matrix_t aMatrix, aInverseMatrix; + cairo_matrix_init( &aMatrix, + aTransform.get( 0, 0 ), aTransform.get( 1, 0 ), aTransform.get( 0, 1 ), + aTransform.get( 1, 1 ), aTransform.get( 0, 2 ), aTransform.get( 1, 2 ) ); + + aMatrix.x0 = basegfx::fround( aMatrix.x0 ); + aMatrix.y0 = basegfx::fround( aMatrix.y0 ); + + cairo_matrix_init( &aInverseMatrix, aMatrix.xx, aMatrix.yx, aMatrix.xy, aMatrix.yy, aMatrix.x0, aMatrix.y0 ); + cairo_matrix_invert( &aInverseMatrix ); + cairo_matrix_transform_distance( &aInverseMatrix, &fX, &fY ); + + cairo_set_matrix( pCairo.get(), &aMatrix ); + } + + fX = basegfx::fround( fX ); + fY = basegfx::fround( fY ); + + cairo_matrix_t aOrigMatrix; + cairo_get_matrix( pCairo.get(), &aOrigMatrix ); + cairo_translate( pCairo.get(), fX, fY ); + + if( getClip().is() ) + { + const uno::Reference<rendering::XPolyPolygon2D>& rClip( getClip() ); + + ::basegfx::B2DPolyPolygon aClipPoly( + ::basegfx::unotools::b2DPolyPolygonFromXPolyPolygon2D( + rClip )); + + doPolyPolygonImplementation( aClipPoly, Clip, pCairo.get(), + nullptr, SurfaceProviderRef(mpSpriteCanvas), + rClip->getFillRule() ); + } + + SAL_INFO( "canvas.cairo","aSize " << aSize.getX() << " x " << aSize.getY() << " position: " << fX << "," << fY ); + cairo_rectangle( pCairo.get(), 0, 0, floor( aSize.getX() ), floor( aSize.getY() ) ); + cairo_clip( pCairo.get() ); + cairo_set_matrix( pCairo.get(), &aOrigMatrix ); + + cairo_matrix_t aInverseMatrix = aOrigMatrix; + bool matrixProblem = false; + // tdf#125949: Cairo internally uses the pixman library, and _cairo_matrix_to_pixman_matrix() + // checks all matrix components to fix PIXMAN_MAX_INT, which is about 32k. Which means that + // if our transformation is large, such as an initial step of some zooming animations, + // the conversion will fail. To make things worse, once something in cairo fails, it's treated + // as a fatal error, the error status of that cairo_t gets set, and there's no way to reset it + // besides recreating the whole cairo_t + // (https://lists.cairographics.org/archives/cairo/2006-September/007892.html). + // So copy&paste PIXMAN_MAX_INT here, and if our matrix could fail, bail out. +#define PIXMAN_MAX_INT ((pixman_fixed_1 >> 1) - pixman_fixed_e) /* need to ensure deltas also fit */ + if(cairo_matrix_invert(&aInverseMatrix) == CAIRO_STATUS_SUCCESS) + { + if(abs(aOrigMatrix.xx) > PIXMAN_MAX_INT || abs(aOrigMatrix.xx) > PIXMAN_MAX_INT + || abs(aOrigMatrix.xy) > PIXMAN_MAX_INT || abs(aOrigMatrix.yx) > PIXMAN_MAX_INT + || abs(aOrigMatrix.x0) > PIXMAN_MAX_INT || abs(aOrigMatrix.y0) > PIXMAN_MAX_INT + || abs(aInverseMatrix.xx) > PIXMAN_MAX_INT || abs(aInverseMatrix.xx) > PIXMAN_MAX_INT + || abs(aInverseMatrix.xy) > PIXMAN_MAX_INT || abs(aInverseMatrix.yx) > PIXMAN_MAX_INT + || abs(aInverseMatrix.x0) > PIXMAN_MAX_INT || abs(aInverseMatrix.y0) > PIXMAN_MAX_INT) + matrixProblem = true; +#undef PIXMAN_MAX_INT + } + else + matrixProblem = true; + if(matrixProblem) + { + SAL_WARN( "canvas.cairo", "matrix would overflow PIXMAN_MAX_INT, avoiding drawing" ); + cairo_restore( pCairo.get() ); + return; + } + + if( isContentFullyOpaque() ) + cairo_set_operator( pCairo.get(), CAIRO_OPERATOR_SOURCE ); + cairo_set_source_surface( pCairo.get(), + mpBufferSurface->getCairoSurface().get(), + fX, fY ); + if( ::rtl::math::approxEqual( fAlpha, 1.0 ) ) + cairo_paint( pCairo.get() ); + else + cairo_paint_with_alpha( pCairo.get(), fAlpha ); + + cairo_restore( pCairo.get() ); + +#ifdef CAIRO_CANVAS_PERF_TRACE + mxDevice->stopPerfTrace( &aTimer, "sprite redraw" ); +#endif + } + + ::basegfx::B2DPolyPolygon SpriteHelper::polyPolygonFromXPolyPolygon2D( uno::Reference< rendering::XPolyPolygon2D >& xPoly ) const + { + return ::basegfx::unotools::b2DPolyPolygonFromXPolyPolygon2D(xPoly); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/cairo/cairo_spritehelper.hxx b/canvas/source/cairo/cairo_spritehelper.hxx new file mode 100644 index 0000000000..df0919e7fd --- /dev/null +++ b/canvas/source/cairo/cairo_spritehelper.hxx @@ -0,0 +1,102 @@ +/* -*- 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 . + */ + +#pragma once + +#include <base/canvascustomspritehelper.hxx> + +#include <basegfx/point/b2dpoint.hxx> + +#include "cairo_spritecanvas.hxx" + + +namespace cairocanvas +{ + /* Definition of SpriteHelper class */ + + /** Helper class for canvas sprites. + + This class implements all sprite-related functionality, like + that available on the XSprite interface. + */ + class SpriteHelper : public ::canvas::CanvasCustomSpriteHelper + { + public: + /** Create sprite helper + */ + SpriteHelper(); + + /** Late-init the sprite helper + + @param rSpriteSize + Size of the sprite + + @param rSpriteCanvas + Sprite canvas this sprite is part of. Object stores + ref-counted reference to it, thus, don't forget to pass on + disposing()! + + @param rDevice + DX device to use + + @param rSpriteSurface + The surface of the sprite (not the DX texture, but the + persistent target of content rendering) + + @param bShowSpriteBounds + When true, little debug bound rects for sprites are shown + */ + void init( const css::geometry::RealSize2D& rSpriteSize, + const SpriteCanvasRef& rSpriteCanvas ); + + void disposing(); + + void setSurface( const ::cairo::SurfaceSharedPtr& pBufferSurface ); + + /** Repaint sprite content to associated sprite canvas + + @param rPos + Output position (sprite's own position is disregarded) + + @param io_bSurfacesDirty + When true, the referenced sprite surfaces (backBuffer and + backBufferMask) have been modified since last call. + + @param bBufferedUpdate + When true, the redraw does <em>not</em> happen directly on + the front buffer, but within a VDev. Used to speed up + drawing. + */ + void redraw( const ::cairo::CairoSharedPtr& pCairo, + const ::basegfx::B2DPoint& rPos, + bool& bSurfacesDirty, + bool bBufferedUpdate ) const; + + private: + virtual ::basegfx::B2DPolyPolygon polyPolygonFromXPolyPolygon2D( + css::uno::Reference< css::rendering::XPolyPolygon2D >& xPoly ) const override; + + + SpriteCanvasRef mpSpriteCanvas; + ::cairo::SurfaceSharedPtr mpBufferSurface; + mutable bool mbTextureDirty; // when true, texture needs update + }; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/cairo/cairo_surfaceprovider.hxx b/canvas/source/cairo/cairo_surfaceprovider.hxx new file mode 100644 index 0000000000..1ff6f2aa7d --- /dev/null +++ b/canvas/source/cairo/cairo_surfaceprovider.hxx @@ -0,0 +1,70 @@ +/* -*- 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 . + */ + +#pragma once + +#include <rtl/ref.hxx> +#include <com/sun/star/uno/XInterface.hpp> + +#include <basegfx/vector/b2isize.hxx> +#include <vcl/cairo.hxx> + +class OutputDevice; +class Bitmap; + +namespace cairocanvas +{ + /* Definition of RepaintTarget interface */ + + /** Target interface for XCachedPrimitive implementations + + This interface must be implemented on all canvas + implementations that hand out XCachedPrimitives + */ + class SAL_LOPLUGIN_ANNOTATE("crosscast") SurfaceProvider : public css::uno::XInterface + { + public: + virtual ~SurfaceProvider() {} + + /** Query surface from this provider + + This should return the default surface to render on. + */ + virtual ::cairo::SurfaceSharedPtr getSurface() = 0; + + /// create new surface in given size + virtual ::cairo::SurfaceSharedPtr createSurface( const ::basegfx::B2ISize& rSize, + int aContent ) = 0; + /// create new surface from given bitmap + virtual ::cairo::SurfaceSharedPtr createSurface( ::Bitmap& rBitmap ) = 0; + + /** convert surface from alpha to non-alpha, does not copy content + channel. returns new surface on success, NULL otherwise + */ + virtual ::cairo::SurfaceSharedPtr changeSurface() = 0; + + /** Provides the underlying vcl outputdevice this surface renders on + */ + virtual OutputDevice* getOutputDevice() = 0; + }; + + typedef ::rtl::Reference< SurfaceProvider > SurfaceProviderRef; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/cairo/cairo_textlayout.cxx b/canvas/source/cairo/cairo_textlayout.cxx new file mode 100644 index 0000000000..8ecd0e0259 --- /dev/null +++ b/canvas/source/cairo/cairo_textlayout.cxx @@ -0,0 +1,363 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> +#include <sal/log.hxx> + +#include <math.h> + +#include <com/sun/star/rendering/TextDirection.hpp> +#include <canvas/canvastools.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/numeric/ftools.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <utility> +#include <vcl/kernarray.hxx> +#include <vcl/metric.hxx> +#include <vcl/virdev.hxx> + +#include "cairo_textlayout.hxx" + +using namespace ::cairo; +using namespace ::com::sun::star; + +namespace cairocanvas +{ + namespace + { + void setupLayoutMode( OutputDevice& rOutDev, + sal_Int8 nTextDirection ) + { + // TODO(P3): avoid if already correctly set + vcl::text::ComplexTextLayoutFlags nLayoutMode = vcl::text::ComplexTextLayoutFlags::Default; + switch( nTextDirection ) + { + case rendering::TextDirection::WEAK_LEFT_TO_RIGHT: + break; + case rendering::TextDirection::STRONG_LEFT_TO_RIGHT: + nLayoutMode = vcl::text::ComplexTextLayoutFlags::BiDiStrong; + break; + case rendering::TextDirection::WEAK_RIGHT_TO_LEFT: + nLayoutMode = vcl::text::ComplexTextLayoutFlags::BiDiRtl; + break; + case rendering::TextDirection::STRONG_RIGHT_TO_LEFT: + nLayoutMode = vcl::text::ComplexTextLayoutFlags::BiDiRtl | vcl::text::ComplexTextLayoutFlags::BiDiStrong; + break; + default: + break; + } + + // set calculated layout mode. Origin is always the left edge, + // as required at the API spec + rOutDev.SetLayoutMode( nLayoutMode | vcl::text::ComplexTextLayoutFlags::TextOriginLeft ); + } + } + + TextLayout::TextLayout( rendering::StringContext aText, + sal_Int8 nDirection, + sal_Int64 /*nRandomSeed*/, + CanvasFont::Reference rFont, + SurfaceProviderRef rRefDevice ) : + maText(std::move( aText )), + mpFont(std::move( rFont )), + mpRefDevice(std::move( rRefDevice )), + mnTextDirection( nDirection ) + { + } + + TextLayout::~TextLayout() + { + } + + void TextLayout::disposing(std::unique_lock<std::mutex>& /*rGuard*/) + { + mpFont.clear(); + mpRefDevice.clear(); + } + + // XTextLayout + uno::Sequence< uno::Reference< rendering::XPolyPolygon2D > > SAL_CALL TextLayout::queryTextShapes( ) + { + // TODO + return uno::Sequence< uno::Reference< rendering::XPolyPolygon2D > >(); + } + + uno::Sequence< geometry::RealRectangle2D > SAL_CALL TextLayout::queryInkMeasures( ) + { + // TODO + return uno::Sequence< geometry::RealRectangle2D >(); + } + + uno::Sequence< geometry::RealRectangle2D > SAL_CALL TextLayout::queryMeasures( ) + { + // TODO + return uno::Sequence< geometry::RealRectangle2D >(); + } + + uno::Sequence< double > SAL_CALL TextLayout::queryLogicalAdvancements( ) + { + std::unique_lock aGuard( m_aMutex ); + + return maLogicalAdvancements; + } + + void SAL_CALL TextLayout::applyLogicalAdvancements( const uno::Sequence< double >& aAdvancements ) + { + std::unique_lock aGuard( m_aMutex ); + + if( aAdvancements.getLength() != maText.Length ) + { + SAL_WARN("canvas.cairo", "TextLayout::applyLogicalAdvancements(): mismatching number of advancements" ); + throw lang::IllegalArgumentException("mismatching number of advancements", getXWeak(), 1); + } + + maLogicalAdvancements = aAdvancements; + } + + uno::Sequence< sal_Bool > SAL_CALL TextLayout::queryKashidaPositions( ) + { + std::unique_lock aGuard( m_aMutex ); + + return maKashidaPositions; + } + + void SAL_CALL TextLayout::applyKashidaPositions( const uno::Sequence< sal_Bool >& aPositions ) + { + std::unique_lock aGuard( m_aMutex ); + + if( aPositions.hasElements() && aPositions.getLength() != maText.Length ) + { + SAL_WARN("canvas.cairo", "TextLayout::applyKashidaPositions(): mismatching number of positions" ); + throw lang::IllegalArgumentException("mismatching number of positions", getXWeak(), 1); + } + + maKashidaPositions = aPositions; + } + + geometry::RealRectangle2D SAL_CALL TextLayout::queryTextBounds( ) + { + std::unique_lock aGuard( m_aMutex ); + + OutputDevice* pOutDev = mpRefDevice->getOutputDevice(); + if( !pOutDev ) + return geometry::RealRectangle2D(); + + ScopedVclPtrInstance< VirtualDevice > pVDev( *pOutDev ); + pVDev->SetFont( mpFont->getVCLFont() ); + + // need metrics for Y offset, the XCanvas always renders + // relative to baseline + const ::FontMetric& aMetric( pVDev->GetFontMetric() ); + + setupLayoutMode( *pVDev, mnTextDirection ); + + const sal_Int32 nAboveBaseline( -aMetric.GetAscent() ); + const sal_Int32 nBelowBaseline( aMetric.GetDescent() ); + + if( maLogicalAdvancements.hasElements() ) + { + return geometry::RealRectangle2D( 0, nAboveBaseline, + maLogicalAdvancements[ maLogicalAdvancements.getLength()-1 ], + nBelowBaseline ); + } + else + { + return geometry::RealRectangle2D( 0, nAboveBaseline, + pVDev->GetTextWidth( + maText.Text, + ::canvas::tools::numeric_cast<sal_uInt16>(maText.StartPosition), + ::canvas::tools::numeric_cast<sal_uInt16>(maText.Length) ), + nBelowBaseline ); + } + } + + double SAL_CALL TextLayout::justify( double /*nSize*/ ) + { + // TODO + return 0.0; + } + + double SAL_CALL TextLayout::combinedJustify( const uno::Sequence< uno::Reference< rendering::XTextLayout > >& /*aNextLayouts*/, + double /*nSize*/ ) + { + // TODO + return 0.0; + } + + rendering::TextHit SAL_CALL TextLayout::getTextHit( const geometry::RealPoint2D& /*aHitPoint*/ ) + { + // TODO + return rendering::TextHit(); + } + + rendering::Caret SAL_CALL TextLayout::getCaret( sal_Int32 /*nInsertionIndex*/, + sal_Bool /*bExcludeLigatures*/ ) + { + // TODO + return rendering::Caret(); + } + + sal_Int32 SAL_CALL TextLayout::getNextInsertionIndex( sal_Int32 /*nStartIndex*/, + sal_Int32 /*nCaretAdvancement*/, + sal_Bool /*bExcludeLigatures*/ ) + { + // TODO + return 0; + } + + uno::Reference< rendering::XPolyPolygon2D > SAL_CALL TextLayout::queryVisualHighlighting( sal_Int32 /*nStartIndex*/, + sal_Int32 /*nEndIndex*/ ) + { + // TODO + return uno::Reference< rendering::XPolyPolygon2D >(); + } + + uno::Reference< rendering::XPolyPolygon2D > SAL_CALL TextLayout::queryLogicalHighlighting( sal_Int32 /*nStartIndex*/, + sal_Int32 /*nEndIndex*/ ) + { + // TODO + return uno::Reference< rendering::XPolyPolygon2D >(); + } + + double SAL_CALL TextLayout::getBaselineOffset( ) + { + // TODO + return 0.0; + } + + sal_Int8 SAL_CALL TextLayout::getMainTextDirection( ) + { + return mnTextDirection; + } + + uno::Reference< rendering::XCanvasFont > SAL_CALL TextLayout::getFont( ) + { + std::unique_lock aGuard( m_aMutex ); + + return mpFont; + } + + rendering::StringContext SAL_CALL TextLayout::getText( ) + { + return maText; + } + + /** + * TextLayout::draw + * + * Cairo-based text rendering. Draw text directly on the cairo surface with cairo fonts. + * Avoid using VCL VirtualDevices for that, bypassing VCL DrawText functions, when possible + * + * Note: some text effects are not rendered due to lacking generic canvas or cairo canvas + * implementation. See issues 92657, 92658, 92659, 92660, 97529 + **/ + void TextLayout::draw( OutputDevice& rOutDev, + const Point& rOutpos, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState ) const + { + std::unique_lock aGuard( m_aMutex ); + setupLayoutMode( rOutDev, mnTextDirection ); + + if (maLogicalAdvancements.hasElements()) + { + KernArray aOffsets(setupTextOffsets(maLogicalAdvancements, viewState, renderState)); + std::span<const sal_Bool> aKashidaArray(maKashidaPositions.getConstArray(), maKashidaPositions.getLength()); + + rOutDev.DrawTextArray( rOutpos, maText.Text, aOffsets, aKashidaArray, + ::canvas::tools::numeric_cast<sal_uInt16>(maText.StartPosition), + ::canvas::tools::numeric_cast<sal_uInt16>(maText.Length) ); + } + else + { + rOutDev.DrawText( rOutpos, maText.Text, + ::canvas::tools::numeric_cast<sal_uInt16>(maText.StartPosition), + ::canvas::tools::numeric_cast<sal_uInt16>(maText.Length) ); + } + } + + namespace + { + class OffsetTransformer + { + public: + explicit OffsetTransformer( ::basegfx::B2DHomMatrix aMat ) : + maMatrix(std::move( aMat )) + { + } + + sal_Int32 operator()( const double& rOffset ) + { + // This is an optimization of the normal rMat*[x,0] + // transformation of the advancement vector (in x + // direction), followed by a length calculation of the + // resulting vector: advancement' = + // ||rMat*[x,0]||. Since advancements are vectors, we + // can ignore translational components, thus if [x,0], + // it follows that rMat*[x,0]=[x',0] holds. Thus, we + // just have to calc the transformation of the x + // component. + + // TODO(F2): Handle non-horizontal advancements! + return ::basegfx::fround( hypot(maMatrix.get(0,0)*rOffset, + maMatrix.get(1,0)*rOffset) ); + } + + private: + ::basegfx::B2DHomMatrix maMatrix; + }; + } + + KernArray TextLayout::setupTextOffsets( + const uno::Sequence< double >& inputOffsets, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState ) const + { + ::basegfx::B2DHomMatrix aMatrix; + + ::canvas::tools::mergeViewAndRenderTransform(aMatrix, + viewState, + renderState); + + // fill integer offsets + KernArray outputOffsets; + OffsetTransformer aTransform(aMatrix); + std::for_each(inputOffsets.begin(), inputOffsets.end(), + [&outputOffsets, &aTransform](double n) {outputOffsets.push_back(aTransform(n)); } ); + return outputOffsets; + } + + OUString SAL_CALL TextLayout::getImplementationName() + { + return "CairoCanvas::TextLayout"; + } + + sal_Bool SAL_CALL TextLayout::supportsService( const OUString& ServiceName ) + { + return cppu::supportsService( this, ServiceName ); + } + + uno::Sequence< OUString > SAL_CALL TextLayout::getSupportedServiceNames() + { + return { "com.sun.star.rendering.TextLayout" }; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/cairo/cairo_textlayout.hxx b/canvas/source/cairo/cairo_textlayout.hxx new file mode 100644 index 0000000000..ed8265e8b3 --- /dev/null +++ b/canvas/source/cairo/cairo_textlayout.hxx @@ -0,0 +1,107 @@ +/* -*- 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 . + */ + +#pragma once + +#include <comphelper/compbase.hxx> + +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/rendering/RenderState.hpp> +#include <com/sun/star/rendering/ViewState.hpp> +#include <com/sun/star/rendering/XTextLayout.hpp> + +#include <vcl/outdev.hxx> + +#include "cairo_canvasfont.hxx" + + +/* Definition of TextLayout class */ + +namespace cairocanvas +{ + typedef ::comphelper::WeakComponentImplHelper< css::rendering::XTextLayout, + css::lang::XServiceInfo > TextLayout_Base; + + class TextLayout : public TextLayout_Base + { + public: + /// make noncopyable + TextLayout(const TextLayout&) = delete; + const TextLayout& operator=(const TextLayout&) = delete; + + TextLayout( css::rendering::StringContext aText, + sal_Int8 nDirection, + sal_Int64 nRandomSeed, + CanvasFont::Reference rFont, + SurfaceProviderRef rRefDevice ); + + /// Dispose all internal references + virtual void disposing(std::unique_lock<std::mutex>& rGuard) override; + + // XTextLayout + virtual css::uno::Sequence< css::uno::Reference< css::rendering::XPolyPolygon2D > > SAL_CALL queryTextShapes( ) override; + virtual css::uno::Sequence< css::geometry::RealRectangle2D > SAL_CALL queryInkMeasures( ) override; + virtual css::uno::Sequence< css::geometry::RealRectangle2D > SAL_CALL queryMeasures( ) override; + virtual css::uno::Sequence< double > SAL_CALL queryLogicalAdvancements( ) override; + virtual void SAL_CALL applyLogicalAdvancements( const css::uno::Sequence< double >& aAdvancements ) override; + virtual css::uno::Sequence< sal_Bool > SAL_CALL queryKashidaPositions( ) override; + virtual void SAL_CALL applyKashidaPositions( const css::uno::Sequence< sal_Bool >& aPositions ) override; + virtual css::geometry::RealRectangle2D SAL_CALL queryTextBounds( ) override; + virtual double SAL_CALL justify( double nSize ) override; + virtual double SAL_CALL combinedJustify( const css::uno::Sequence< css::uno::Reference< css::rendering::XTextLayout > >& aNextLayouts, double nSize ) override; + virtual css::rendering::TextHit SAL_CALL getTextHit( const css::geometry::RealPoint2D& aHitPoint ) override; + virtual css::rendering::Caret SAL_CALL getCaret( sal_Int32 nInsertionIndex, sal_Bool bExcludeLigatures ) override; + virtual sal_Int32 SAL_CALL getNextInsertionIndex( sal_Int32 nStartIndex, sal_Int32 nCaretAdvancement, sal_Bool bExcludeLigatures ) override; + virtual css::uno::Reference< css::rendering::XPolyPolygon2D > SAL_CALL queryVisualHighlighting( sal_Int32 nStartIndex, sal_Int32 nEndIndex ) override; + virtual css::uno::Reference< css::rendering::XPolyPolygon2D > SAL_CALL queryLogicalHighlighting( sal_Int32 nStartIndex, sal_Int32 nEndIndex ) override; + virtual double SAL_CALL getBaselineOffset( ) override; + virtual sal_Int8 SAL_CALL getMainTextDirection( ) override; + virtual css::uno::Reference< css::rendering::XCanvasFont > SAL_CALL getFont( ) override; + virtual css::rendering::StringContext SAL_CALL getText( ) override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override; + + void draw( OutputDevice& rOutDev, + const Point& rOutpos, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState ) const; + + KernArray setupTextOffsets( + const css::uno::Sequence< double >& inputOffsets, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState ) const; + + protected: + virtual ~TextLayout() override; // we're a ref-counted UNO class. _We_ destroy ourselves. + + private: + css::rendering::StringContext maText; + css::uno::Sequence< double > maLogicalAdvancements; + css::uno::Sequence< sal_Bool > maKashidaPositions; + CanvasFont::Reference mpFont; + SurfaceProviderRef mpRefDevice; + sal_Int8 mnTextDirection; + }; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/cairo/cairocanvas.component b/canvas/source/cairo/cairocanvas.component new file mode 100644 index 0000000000..7a201f582e --- /dev/null +++ b/canvas/source/cairo/cairocanvas.component @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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 . + --> + +<component loader="com.sun.star.loader.SharedLibrary" environment="@CPPU_ENV@" + xmlns="http://openoffice.org/2010/uno-components"> + <implementation name="com.sun.star.comp.rendering.Canvas.Cairo" + constructor="com_sun_star_comp_rendering_Canvas_Cairo_get_implementation"> + <service name="com.sun.star.rendering.Canvas.Cairo"/> + </implementation> + <implementation name="com.sun.star.comp.rendering.SpriteCanvas.Cairo" + constructor="com_sun_star_comp_rendering_SpriteCanvas_Cairo_get_implementation"> + <service name="com.sun.star.rendering.SpriteCanvas.Cairo"/> + </implementation> +</component> diff --git a/canvas/source/directx/directx9canvas.component b/canvas/source/directx/directx9canvas.component new file mode 100644 index 0000000000..ac75940d2c --- /dev/null +++ b/canvas/source/directx/directx9canvas.component @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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 . + --> + +<component loader="com.sun.star.loader.SharedLibrary" environment="@CPPU_ENV@" + xmlns="http://openoffice.org/2010/uno-components"> + <implementation name="com.sun.star.comp.rendering.SpriteCanvas.DX9" + constructor="canvas_directx9_SpriteCanvas_get_implementation"> + <service name="com.sun.star.rendering.SpriteCanvas.DX9"/> + </implementation> +</component> diff --git a/canvas/source/directx/dx_9rm.cxx b/canvas/source/directx/dx_9rm.cxx new file mode 100644 index 0000000000..93738b3455 --- /dev/null +++ b/canvas/source/directx/dx_9rm.cxx @@ -0,0 +1,1201 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> +#include <sal/log.hxx> + +#include <memory> +#include <string.h> + +#include <basegfx/numeric/ftools.hxx> +#include <basegfx/point/b2ipoint.hxx> +#include <basegfx/range/b2irectangle.hxx> +#include <basegfx/vector/b2dsize.hxx> +#include <basegfx/vector/b2isize.hxx> +#include <com/sun/star/lang/NoSupportException.hpp> +#include <osl/thread.hxx> +#include <osl/time.h> +#include <comphelper/diagnose_ex.hxx> +#include <vcl/syschild.hxx> +#include <vcl/sysdata.hxx> +#include <vcl/window.hxx> + +#include <canvas/elapsedtime.hxx> +#include <canvas/canvastools.hxx> +#include <rendering/icolorbuffer.hxx> +#include <rendering/irendermodule.hxx> +#include <rendering/isurface.hxx> + +#include "dx_config.hxx" +#include "dx_impltools.hxx" +#include "dx_rendermodule.hxx" + +#define MIN_TEXTURE_SIZE (32) +//#define FAKE_MAX_NUMBER_TEXTURES (2) +//#define FAKE_MAX_TEXTURE_SIZE (4096) + +#define VERTEX_BUFFER_SIZE (341*3) // 1023, the size of the internal + // vertex buffer (must be divisible + // by 3, as each triangle primitive + // has 3 vertices) + + +using namespace ::com::sun::star; + + +// 'dxcanvas' namespace + + +namespace dxcanvas +{ + namespace + { + class DXRenderModule; + + + // DXSurface + + + /** ISurface implementation. + + @attention holds the DXRenderModule via non-refcounted + reference! This is safe with current state of affairs, since + the canvas::PageManager holds surface and render module via + shared_ptr (and makes sure all surfaces are deleted before its + render module member goes out of scope). + */ + class DXSurface : public canvas::ISurface + { + public: + DXSurface( DXRenderModule& rRenderModule, + const ::basegfx::B2ISize& rSize ); + ~DXSurface() override; + + virtual bool selectTexture() override; + virtual bool isValid() override; + virtual bool update( const ::basegfx::B2IPoint& rDestPos, + const ::basegfx::B2IRange& rSourceRect, + ::canvas::IColorBuffer& rSource ) override; + virtual ::basegfx::B2ISize getSize(); + + private: + /// Guard local methods against concurrent access to RenderModule + class ImplRenderModuleGuard + { + public: + /// make noncopyable + ImplRenderModuleGuard(const ImplRenderModuleGuard&) = delete; + const ImplRenderModuleGuard& operator=(const ImplRenderModuleGuard&) = delete; + + explicit ImplRenderModuleGuard( DXRenderModule& rRenderModule ); + ~ImplRenderModuleGuard(); + + private: + DXRenderModule& mrRenderModule; + }; + + DXRenderModule& mrRenderModule; + sal::systools::COMReference<IDirect3DTexture9> mpTexture; + + ::basegfx::B2ISize maSize; + }; + + + // DXRenderModule + + + /// Default implementation of IDXRenderModule + class DXRenderModule final: public IDXRenderModule + { + public: + explicit DXRenderModule( const vcl::Window& rWindow ); + ~DXRenderModule() override; + + virtual void lock() const override { maMutex.acquire(); } + virtual void unlock() const override { maMutex.release(); } + + virtual sal::systools::COMReference<IDirect3DSurface9> + createSystemMemorySurface(const ::basegfx::B2ISize& rSize) override; + virtual void disposing() override; + virtual HWND getHWND() const override { return mhWnd; } + virtual void screenShot() override; + + virtual bool flip( const ::basegfx::B2IRectangle& rUpdateArea, + const ::basegfx::B2IRectangle& rCurrWindowArea ) override; + + virtual void resize( const ::basegfx::B2IRange& rect ) override; + virtual ::basegfx::B2IVector getPageSize() override; + virtual std::shared_ptr<canvas::ISurface> createSurface( const ::basegfx::B2IVector& surfaceSize ) override; + virtual void beginPrimitive( PrimitiveType eType ) override; + virtual void endPrimitive() override; + virtual void pushVertex( const ::canvas::Vertex& vertex ) override; + virtual bool isError() override; + + sal::systools::COMReference<IDirect3DDevice9> getDevice() { return mpDevice; } + + void flushVertexCache(); + void commitVertexCache(); + + private: + + bool create( const vcl::Window& rWindow ); + bool createDevice(); + bool verifyDevice( const UINT nAdapter ); + UINT getAdapterFromWindow(); + + /** This object represents the DirectX state machine. In order + to serialize access to DirectX's global state, a global + mutex is required. + */ + static ::osl::Mutex maMutex; + + HWND mhWnd; + sal::systools::COMReference<IDirect3DDevice9> mpDevice; + sal::systools::COMReference<IDirect3D9> mpDirect3D9; + sal::systools::COMReference<IDirect3DSwapChain9> mpSwapChain; + sal::systools::COMReference<IDirect3DVertexBuffer9> mpVertexBuffer; + std::shared_ptr<canvas::ISurface> mpTexture; + VclPtr<SystemChildWindow> mpWindow; + ::basegfx::B2ISize maSize; + typedef std::vector<canvas::Vertex> vertexCache_t; + vertexCache_t maVertexCache; + std::size_t mnCount; + int mnBeginSceneCount; + bool mbCanUseDynamicTextures; + bool mbError; + PrimitiveType meType; + ::basegfx::B2IVector maPageSize; + D3DPRESENT_PARAMETERS mad3dpp; + + bool isDisposed() const { return (mhWnd==nullptr); } + + struct dxvertex + { + float x,y,z,rhw; + DWORD diffuse; + float u,v; + }; + + std::size_t maNumVertices; + std::size_t maWriteIndex; + std::size_t maReadIndex; + }; + + ::osl::Mutex DXRenderModule::maMutex; + + + // DXSurface::ImplRenderModuleGuard + + + DXSurface::ImplRenderModuleGuard::ImplRenderModuleGuard( + DXRenderModule& rRenderModule ) : + mrRenderModule( rRenderModule ) + { + mrRenderModule.lock(); + } + + DXSurface::ImplRenderModuleGuard::~ImplRenderModuleGuard() + { + mrRenderModule.unlock(); + } + +#ifdef FAKE_MAX_NUMBER_TEXTURES + static sal_uInt32 gNumSurfaces = 0; +#endif + + // DXSurface::DXSurface + + + DXSurface::DXSurface( DXRenderModule& rRenderModule, + const ::basegfx::B2ISize& rSize ) : + mrRenderModule(rRenderModule), + mpTexture(nullptr) + { + ImplRenderModuleGuard aGuard( mrRenderModule ); + +#ifdef FAKE_MAX_NUMBER_TEXTURES + ++gNumSurfaces; + if(gNumSurfaces >= FAKE_MAX_NUMBER_TEXTURES) + return; +#endif + +#ifdef FAKE_MAX_TEXTURE_SIZE + if(rSize.getWidth() > FAKE_MAX_TEXTURE_SIZE) + return; + if(rSize.getHeight() > FAKE_MAX_TEXTURE_SIZE) + return; +#endif + + ENSURE_ARG_OR_THROW(rSize.getWidth() > 0 && rSize.getHeight() > 0, + "DXSurface::DXSurface(): request for zero-sized surface"); + + sal::systools::COMReference<IDirect3DDevice9> pDevice(rRenderModule.getDevice()); + + IDirect3DTexture9 *pTexture(nullptr); + if(FAILED(pDevice->CreateTexture( + rSize.getWidth(), + rSize.getHeight(), + 1,0,D3DFMT_A8R8G8B8, + D3DPOOL_MANAGED, + &pTexture,nullptr))) + return; + + mpTexture = sal::systools::COMReference<IDirect3DTexture9>(pTexture, false); + maSize = rSize; + } + + + // DXSurface::~DXSurface + + + DXSurface::~DXSurface() + { + ImplRenderModuleGuard aGuard( mrRenderModule ); + +#ifdef FAKE_MAX_NUMBER_TEXTURES + gNumSurfaces--; +#endif + } + + + // DXSurface::selectTexture + + + bool DXSurface::selectTexture() + { + ImplRenderModuleGuard aGuard( mrRenderModule ); + mrRenderModule.flushVertexCache(); + sal::systools::COMReference<IDirect3DDevice9> pDevice(mrRenderModule.getDevice()); + + if( FAILED(pDevice->SetTexture(0,mpTexture.get())) ) + return false; + + return true; + } + + + // DXSurface::isValid + + + bool DXSurface::isValid() + { + ImplRenderModuleGuard aGuard( mrRenderModule ); + + if(!(mpTexture.is())) + return false; + return true; + } + + + // DXSurface::update + + + bool DXSurface::update( const ::basegfx::B2IPoint& rDestPos, + const ::basegfx::B2IRange& rSourceRect, + ::canvas::IColorBuffer& rSource ) + { + ImplRenderModuleGuard aGuard( mrRenderModule ); + + // can't update if surface is not valid, that means + // either not existent nor restored... + if(!(isValid())) + return false; + + D3DLOCKED_RECT aLockedRect; + RECT rect; + rect.left = std::max(sal_Int32(0),rDestPos.getX()); + rect.top = std::max(sal_Int32(0),rDestPos.getY()); + // to avoid interpolation artifacts from other textures, + // the surface manager allocates one pixel gap between + // them. Clear that to transparent. + rect.right = std::min(maSize.getWidth(), + rect.left + sal_Int32(rSourceRect.getWidth()+1)); + rect.bottom = std::min(maSize.getHeight(), + rect.top + sal_Int32(rSourceRect.getHeight()+1)); + const bool bClearRightColumn( rect.right < maSize.getWidth() ); + const bool bClearBottomRow( rect.bottom < maSize.getHeight() ); + + if(SUCCEEDED(mpTexture->LockRect(0,&aLockedRect,&rect,D3DLOCK_NOSYSLOCK))) + { + if(sal_uInt8* pImage = rSource.lock()) + { + switch( rSource.getFormat() ) + { + case ::canvas::IColorBuffer::Format::A8R8G8B8: + { + const std::size_t nSourceBytesPerPixel(4); + const std::size_t nSourcePitchInBytes(rSource.getStride()); + pImage += rSourceRect.getMinY()*nSourcePitchInBytes; + pImage += rSourceRect.getMinX()*nSourceBytesPerPixel; + + // calculate the destination memory address + sal_uInt8 *pDst = static_cast<sal_uInt8*>(aLockedRect.pBits); + + const sal_uInt32 nNumBytesToCopy( + static_cast<sal_uInt32>( + rSourceRect.getWidth())* + nSourceBytesPerPixel); + const sal_uInt64 nNumLines(rSourceRect.getHeight()); + + for(sal_uInt64 i=0; i<nNumLines; ++i) + { + memcpy(pDst,pImage,nNumBytesToCopy); + + if( bClearRightColumn ) + { + // to avoid interpolation artifacts + // from other textures, the surface + // manager allocates one pixel gap + // between them. Clear that to + // transparent. + pDst[nNumBytesToCopy] = + pDst[nNumBytesToCopy+1] = + pDst[nNumBytesToCopy+2] = + pDst[nNumBytesToCopy+3] = 0x00; + } + pDst += aLockedRect.Pitch; + pImage += nSourcePitchInBytes; + } + + if( bClearBottomRow ) + memset(pDst, 0, nNumBytesToCopy+4); + } + break; + + case ::canvas::IColorBuffer::Format::X8R8G8B8: + { + const std::size_t nSourceBytesPerPixel(4); + const std::size_t nSourcePitchInBytes(rSource.getStride()); + pImage += rSourceRect.getMinY()*nSourcePitchInBytes; + pImage += rSourceRect.getMinX()*nSourceBytesPerPixel; + + // calculate the destination memory address + sal_uInt8 *pDst = static_cast<sal_uInt8*>(aLockedRect.pBits); + + const sal_Int32 nNumLines( + sal::static_int_cast<sal_Int32>(rSourceRect.getHeight())); + const sal_Int32 nNumColumns( + sal::static_int_cast<sal_Int32>(rSourceRect.getWidth())); + for(sal_Int32 i=0; i<nNumLines; ++i) + { + sal_uInt32 *pSrc32 = reinterpret_cast<sal_uInt32 *>(pImage); + sal_uInt32 *pDst32 = reinterpret_cast<sal_uInt32 *>(pDst); + for(sal_Int32 j=0; j<nNumColumns; ++j) + pDst32[j] = 0xFF000000 | pSrc32[j]; + + if( bClearRightColumn ) + pDst32[nNumColumns] = 0xFF000000; + + pDst += aLockedRect.Pitch; + pImage += nSourcePitchInBytes; + } + + if( bClearBottomRow ) + memset(pDst, 0, 4*(nNumColumns+1)); + } + break; + + default: + ENSURE_OR_RETURN_FALSE(false, + "DXSurface::update(): Unknown/unimplemented buffer format" ); + break; + } + + rSource.unlock(); + } + + return SUCCEEDED(mpTexture->UnlockRect(0)); + } + + return true; + } + + ::basegfx::B2ISize DXSurface::getSize() + { + return maSize; + } + + DXRenderModule::DXRenderModule( const vcl::Window& rWindow ) : + mhWnd(nullptr), + mpDevice(), + mpDirect3D9(), + mpSwapChain(), + mpVertexBuffer(), + mpTexture(), + maVertexCache(), + mnCount(0), + mnBeginSceneCount(0), + mbCanUseDynamicTextures(false), + mbError( false ), + meType( PrimitiveType::Unknown ), + mad3dpp(), + maNumVertices( VERTEX_BUFFER_SIZE ), + maWriteIndex(0), + maReadIndex(0) + { + // TODO(P2): get rid of those fine-grained locking + ::osl::MutexGuard aGuard( maMutex ); + + if(!(create(rWindow))) + { + throw lang::NoSupportException( "Could not create DirectX device!" ); + } + + // allocate a single texture surface which can be used later. + // we also use this to calibrate the page size. + basegfx::B2IVector aPageSize(maPageSize); + while(true) + { + mpTexture = std::make_shared<DXSurface>(*this, basegfx::B2ISize(aPageSize.getX(), aPageSize.getY())); + if(mpTexture->isValid()) + break; + + aPageSize.setX(aPageSize.getX()>>1); + aPageSize.setY(aPageSize.getY()>>1); + if((aPageSize.getX() < MIN_TEXTURE_SIZE) || + (aPageSize.getY() < MIN_TEXTURE_SIZE)) + { + throw lang::NoSupportException( + "Could not create DirectX device - insufficient texture space!" ); + } + } + maPageSize=aPageSize; + + IDirect3DVertexBuffer9 *pVB(nullptr); + if( FAILED(mpDevice->CreateVertexBuffer(sizeof(dxvertex)*maNumVertices, + D3DUSAGE_DYNAMIC|D3DUSAGE_WRITEONLY, + D3DFVF_XYZRHW|D3DFVF_DIFFUSE|D3DFVF_TEX1, + D3DPOOL_DEFAULT, + &pVB, + nullptr)) ) + { + throw lang::NoSupportException( + "Could not create DirectX device - out of memory!" ); + } + + mpVertexBuffer = sal::systools::COMReference<IDirect3DVertexBuffer9>(pVB, false); + } + + + // DXRenderModule::~DXRenderModule + + + DXRenderModule::~DXRenderModule() + { + disposing(); + } + + + // DXRenderModule::disposing + + + void DXRenderModule::disposing() + { + if(!mhWnd) + return; + + mpTexture.reset(); + mpWindow.disposeAndClear(); + mhWnd=nullptr; + + // refrain from releasing the DX9 objects. We're the only + // ones holding references to them, and it might be + // dangerous to destroy the DX9 device, before all other + // objects are dead. + } + + + // DXRenderModule::create + + + bool DXRenderModule::create( const vcl::Window& rWindow ) + { + // TODO(P2): get rid of those fine-grained locking + ::osl::MutexGuard aGuard( maMutex ); + + // TODO(F2): since we would like to share precious hardware + // resources, the direct3d9 object should be global. each new + // request for a canvas should only create a new swapchain. + mpDirect3D9 = sal::systools::COMReference<IDirect3D9>( + Direct3DCreate9(D3D_SDK_VERSION), false); + if(!mpDirect3D9.is()) + return false; + + maVertexCache.reserve( 1024 ); + + mpWindow.disposeAndClear(); + mpWindow.reset( VclPtr<SystemChildWindow>::Create( + const_cast<vcl::Window *>(&rWindow), 0) ); + + // system child window must not receive mouse events + mpWindow->SetMouseTransparent( true ); + + // parent should receive paint messages as well + mpWindow->SetParentClipMode(ParentClipMode::NoClip); + + // the system child window must not clear its background + mpWindow->EnableEraseBackground( false ); + + mpWindow->SetControlForeground(); + mpWindow->SetControlBackground(); + + const SystemEnvData *pData = mpWindow->GetSystemData(); + const HWND hwnd(reinterpret_cast<HWND>(pData->hWnd)); + mhWnd = hwnd; + + ENSURE_OR_THROW( IsWindow( mhWnd ), + "DXRenderModule::create() No valid HWND given." ); + + // retrieve position and size of the parent window + const ::Size &rSizePixel(rWindow.GetSizePixel()); + + // remember the size of the parent window, since we + // need to use this for our child window. + maSize.setWidth(sal_Int32(rSizePixel.Width())); + maSize.setHeight(sal_Int32(rSizePixel.Height())); + + // let the child window cover the same size as the parent window. + mpWindow->setPosSizePixel(0, 0, maSize.getWidth(),maSize.getHeight()); + + // create a device from the direct3d9 object. + if(!(createDevice())) + { + mpWindow.disposeAndClear(); + return false; + } + + mpWindow->Show(); + + return true; + } + + + // DXRenderModule::verifyDevice + + + bool DXRenderModule::verifyDevice( const UINT nAdapter ) + { + ENSURE_OR_THROW( mpDirect3D9.is(), + "DXRenderModule::verifyDevice() No valid device." ); + + // ask direct3d9 about the capabilities of hardware devices on a specific adapter. + // here we decide if the underlying hardware of the machine 'is good enough'. + // since we only need a tiny little fraction of what could be used, this + // is basically a no-op. + D3DCAPS9 aCaps; + if(FAILED(mpDirect3D9->GetDeviceCaps(nAdapter,D3DDEVTYPE_HAL,&aCaps))) + return false; + if(!(aCaps.MaxTextureWidth)) + return false; + if(!(aCaps.MaxTextureHeight)) + return false; + maPageSize = ::basegfx::B2IVector(aCaps.MaxTextureWidth,aCaps.MaxTextureHeight); + + // check device against allow & denylist entries + D3DADAPTER_IDENTIFIER9 aIdent; + if(FAILED(mpDirect3D9->GetAdapterIdentifier(nAdapter,0,&aIdent))) + return false; + + DXCanvasItem aConfigItem; + DXCanvasItem::DeviceInfo aInfo; + aInfo.nVendorId = aIdent.VendorId; + aInfo.nDeviceId = aIdent.DeviceId; + aInfo.nDeviceSubSysId = aIdent.SubSysId; + aInfo.nDeviceRevision = aIdent.Revision; + + aInfo.nDriverId = HIWORD(aIdent.DriverVersion.HighPart); + aInfo.nDriverVersion = LOWORD(aIdent.DriverVersion.HighPart); + aInfo.nDriverSubVersion = HIWORD(aIdent.DriverVersion.LowPart); + aInfo.nDriverBuildId = LOWORD(aIdent.DriverVersion.LowPart); + + if( !aConfigItem.isDeviceUsable(aInfo) ) + return false; + + if( aConfigItem.isDenylistCurrentDevice() ) + { + aConfigItem.denylistDevice(aInfo); + return false; + } + + aConfigItem.adaptMaxTextureSize(maPageSize); + + mbCanUseDynamicTextures = (aCaps.Caps2 & D3DCAPS2_DYNAMICTEXTURES) != 0; + + return true; + } + + + // DXRenderModule::createDevice + + + bool DXRenderModule::createDevice() + { + // we expect that the caller provides us with a valid HWND + ENSURE_OR_THROW( IsWindow(mhWnd), + "DXRenderModule::createDevice() No valid HWND given." ); + + // we expect that the caller already created the direct3d9 object. + ENSURE_OR_THROW( mpDirect3D9.is(), + "DXRenderModule::createDevice() no direct3d?." ); + + // find the adapter identifier from the window. + const UINT aAdapter(getAdapterFromWindow()); + if(aAdapter == static_cast<UINT>(-1)) + return false; + + // verify that device possibly works + if( !verifyDevice(aAdapter) ) + return false; + + // query the display mode from the selected adapter. + // we'll later request the backbuffer format to be same + // same as the display format. + D3DDISPLAYMODE d3ddm; + mpDirect3D9->GetAdapterDisplayMode(aAdapter,&d3ddm); + + // we need to use D3DSWAPEFFECT_COPY here since the canvas-api has + // basically nothing to do with efficient resource handling. it tries + // to avoid drawing whenever possible, which is simply not the most + // efficient way we could leverage the hardware in this case. it would + // be far better to redraw the backbuffer each time we would like to + // display the content of the backbuffer, but we need to face reality + // here and follow how the canvas was designed. + + // Strictly speaking, we don't need a full screen worth of + // backbuffer here. We could also scale dynamically with + // the current window size, but this will make it + // necessary to temporarily have two buffers while copying + // from the old to the new one. What's more, at the time + // we need a larger buffer, DX might not have sufficient + // resources available, and we're then left with too small + // a back buffer, and no way of falling back to a + // different canvas implementation. + ZeroMemory( &mad3dpp, sizeof(mad3dpp) ); + mad3dpp.BackBufferWidth = std::max(maSize.getWidth(), sal_Int32(d3ddm.Width)); + mad3dpp.BackBufferHeight = std::max(maSize.getHeight(), sal_Int32(d3ddm.Height)); + mad3dpp.BackBufferCount = 1; + mad3dpp.Windowed = TRUE; + mad3dpp.SwapEffect = D3DSWAPEFFECT_COPY; + mad3dpp.BackBufferFormat = d3ddm.Format; + mad3dpp.EnableAutoDepthStencil = FALSE; + mad3dpp.hDeviceWindow = mhWnd; + mad3dpp.PresentationInterval = D3DPRESENT_INTERVAL_ONE; + + // now create the device, first try hardware vertex processing, + // then software vertex processing. if both queries fail, we give up + // and indicate failure. + IDirect3DDevice9 *pDevice(nullptr); + if(FAILED(mpDirect3D9->CreateDevice(aAdapter, + D3DDEVTYPE_HAL, + mhWnd, + D3DCREATE_HARDWARE_VERTEXPROCESSING| + D3DCREATE_MULTITHREADED|D3DCREATE_FPU_PRESERVE, + &mad3dpp, + &pDevice))) + if(FAILED(mpDirect3D9->CreateDevice(aAdapter, + D3DDEVTYPE_HAL, + mhWnd, + D3DCREATE_SOFTWARE_VERTEXPROCESSING| + D3DCREATE_MULTITHREADED|D3DCREATE_FPU_PRESERVE, + &mad3dpp, + &pDevice))) + return false; + + // got it, store it in a safe place... + mpDevice = sal::systools::COMReference<IDirect3DDevice9>(pDevice, false); + + // After CreateDevice, the first swap chain already exists, so just get it... + IDirect3DSwapChain9 *pSwapChain(nullptr); + pDevice->GetSwapChain(0,&pSwapChain); + mpSwapChain = sal::systools::COMReference<IDirect3DSwapChain9>(pSwapChain, false); + if( !mpSwapChain.is() ) + return false; + + // clear the render target [which is the backbuffer in this case]. + // we are forced to do this once, and furthermore right now. + // please note that this is only possible since we created the + // backbuffer with copy semantics [the content is preserved after + // calls to Present()], which is an unnecessarily expensive operation. + LPDIRECT3DSURFACE9 pBackBuffer = nullptr; + mpSwapChain->GetBackBuffer(0,D3DBACKBUFFER_TYPE_MONO,&pBackBuffer); + mpDevice->SetRenderTarget( 0, pBackBuffer ); + mpDevice->Clear(0,nullptr,D3DCLEAR_TARGET,0,1.0f,0); + pBackBuffer->Release(); + + return true; + } + + + // DXRenderModule::createSystemMemorySurface + + + sal::systools::COMReference<IDirect3DSurface9> DXRenderModule::createSystemMemorySurface(const ::basegfx::B2ISize& rSize) + { + if(isDisposed()) + return sal::systools::COMReference<IDirect3DSurface9>(nullptr); + + // please note that D3DFMT_X8R8G8B8 is the only format we're + // able to choose here, since GetDC() doesn't support any + // other 32bit-format. + IDirect3DSurface9 *pSurface(nullptr); + if( FAILED(mpDevice->CreateOffscreenPlainSurface( + rSize.getWidth(), + rSize.getHeight(), + D3DFMT_X8R8G8B8, + D3DPOOL_SYSTEMMEM, + &pSurface, + nullptr)) ) + { + throw lang::NoSupportException( + "Could not create offscreen surface - out of mem!" ); + } + + return sal::systools::COMReference<IDirect3DSurface9>(pSurface, false); + } + + + // DXRenderModule::flip + + + bool DXRenderModule::flip( const ::basegfx::B2IRectangle& rUpdateArea, + const ::basegfx::B2IRectangle& /*rCurrWindowArea*/ ) + { + // TODO(P2): get rid of those fine-grained locking + ::osl::MutexGuard aGuard( maMutex ); + + if(isDisposed() || !mpSwapChain.is()) + return false; + + flushVertexCache(); + + // TODO(P2): Might be faster to actually pass update area here + RECT aRect = + { + rUpdateArea.getMinX(), + rUpdateArea.getMinY(), + rUpdateArea.getMaxX(), + rUpdateArea.getMaxY() + }; + HRESULT hr(mpSwapChain->Present(&aRect,&aRect,nullptr,nullptr,0)); + if(FAILED(hr)) + { + if(hr != D3DERR_DEVICELOST) + return false; + + // interestingly enough, sometimes the Reset() below + // *still* causes DeviceLost errors. So, cycle until + // DX was kind enough to really reset the device... + do + { + mpVertexBuffer.clear(); + hr = mpDevice->Reset(&mad3dpp); + if(SUCCEEDED(hr)) + { + IDirect3DVertexBuffer9 *pVB(nullptr); + if( FAILED(mpDevice->CreateVertexBuffer(sizeof(dxvertex)*maNumVertices, + D3DUSAGE_DYNAMIC|D3DUSAGE_WRITEONLY, + D3DFVF_XYZRHW|D3DFVF_DIFFUSE|D3DFVF_TEX1, + D3DPOOL_DEFAULT, + &pVB, + nullptr)) ) + { + throw lang::NoSupportException( + "Could not create DirectX device - out of memory!" ); + } + mpVertexBuffer = sal::systools::COMReference<IDirect3DVertexBuffer9>(pVB, false); + + // retry after the restore + if(SUCCEEDED(mpSwapChain->Present(&aRect,&aRect,nullptr,nullptr,0))) + return true; + } + + osl::Thread::wait(std::chrono::seconds(1)); + } + while(hr == D3DERR_DEVICELOST); + + return false; + } + + return true; + } + + + // DXRenderModule::screenShot + + + void DXRenderModule::screenShot() + { + } + + + // DXRenderModule::resize + + + void DXRenderModule::resize( const ::basegfx::B2IRange& rect ) + { + // TODO(P2): get rid of those fine-grained locking + ::osl::MutexGuard aGuard( maMutex ); + + if(isDisposed()) + return; + + // don't do anything if the size didn't change. + if(maSize.getWidth() == static_cast<sal_Int32>(rect.getWidth()) && + maSize.getHeight() == static_cast<sal_Int32>(rect.getHeight())) + return; + + // TODO(Q2): use numeric cast to prevent overflow + maSize.setWidth(sal_Int32(rect.getWidth())); + maSize.setHeight(sal_Int32(rect.getHeight())); + + mpWindow->setPosSizePixel(0, 0, maSize.getWidth(), maSize.getHeight()); + + // resize back buffer, if necessary + + + // don't attempt to create anything if the + // requested size is NULL. + if(!(maSize.getWidth())) + return; + if(!(maSize.getHeight())) + return; + + // backbuffer too small (might happen, if window is + // maximized across multiple monitors) + if( sal_Int32(mad3dpp.BackBufferWidth) < maSize.getWidth() || + sal_Int32(mad3dpp.BackBufferHeight) < maSize.getHeight() ) + { + mad3dpp.BackBufferWidth = maSize.getWidth(); + mad3dpp.BackBufferHeight = maSize.getHeight(); + + // clear before, save resources + mpSwapChain.clear(); + + IDirect3DSwapChain9 *pSwapChain(nullptr); + if(FAILED(mpDevice->CreateAdditionalSwapChain(&mad3dpp,&pSwapChain))) + return; + mpSwapChain = sal::systools::COMReference<IDirect3DSwapChain9>(pSwapChain, false); + + // clear the render target [which is the backbuffer in this case]. + // we are forced to do this once, and furthermore right now. + // please note that this is only possible since we created the + // backbuffer with copy semantics [the content is preserved after + // calls to Present()], which is an unnecessarily expensive operation. + LPDIRECT3DSURFACE9 pBackBuffer = nullptr; + mpSwapChain->GetBackBuffer(0,D3DBACKBUFFER_TYPE_MONO,&pBackBuffer); + mpDevice->SetRenderTarget( 0, pBackBuffer ); + mpDevice->Clear(0,nullptr,D3DCLEAR_TARGET,0,1.0f,0); + pBackBuffer->Release(); + } + } + + + // DXRenderModule::getPageSize + + + ::basegfx::B2IVector DXRenderModule::getPageSize() + { + // TODO(P2): get rid of those fine-grained locking + ::osl::MutexGuard aGuard( maMutex ); + return maPageSize; + } + + + // DXRenderModule::createSurface + + + std::shared_ptr<canvas::ISurface> DXRenderModule::createSurface( const ::basegfx::B2IVector& surfaceSize ) + { + // TODO(P2): get rid of those fine-grained locking + ::osl::MutexGuard aGuard( maMutex ); + + if(isDisposed()) + return std::shared_ptr<canvas::ISurface>(); + + const ::basegfx::B2IVector& rPageSize( getPageSize() ); + ::basegfx::B2ISize aSize(surfaceSize); + if(!(aSize.getWidth())) + aSize.setWidth(rPageSize.getX()); + if(!(aSize.getHeight())) + aSize.setHeight(rPageSize.getY()); + + if(mpTexture.use_count() == 1) + return mpTexture; + + return std::make_shared<DXSurface>(*this,aSize); + } + + + // DXRenderModule::beginPrimitive + + + void DXRenderModule::beginPrimitive( PrimitiveType eType ) + { + // TODO(P2): get rid of those fine-grained locking + ::osl::MutexGuard aGuard( maMutex ); + + if(isDisposed()) + return; + + ENSURE_OR_THROW( !mnBeginSceneCount, + "DXRenderModule::beginPrimitive(): nested call" ); + + ++mnBeginSceneCount; + meType=eType; + mnCount=0; + } + + + // DXRenderModule::endPrimitive + + + void DXRenderModule::endPrimitive() + { + // TODO(P2): get rid of those fine-grained locking + ::osl::MutexGuard aGuard( maMutex ); + + if(isDisposed()) + return; + + --mnBeginSceneCount; + meType = PrimitiveType::Unknown; + mnCount = 0; + } + + + // DXRenderModule::pushVertex + + + void DXRenderModule::pushVertex( const ::canvas::Vertex& vertex ) + { + // TODO(P2): get rid of those fine-grained locking + ::osl::MutexGuard aGuard( maMutex ); + + if(isDisposed()) + return; + + switch(meType) + { + case PrimitiveType::Triangle: + { + maVertexCache.push_back(vertex); + ++mnCount; + mnCount &= 3; + break; + } + + case PrimitiveType::Quad: + { + if(mnCount == 3) + { + const std::size_t size(maVertexCache.size()); + ::canvas::Vertex v0(maVertexCache[size-1]); + ::canvas::Vertex v2(maVertexCache[size-3]); + maVertexCache.push_back(v0); + maVertexCache.push_back(vertex); + maVertexCache.push_back(v2); + mnCount=0; + } + else + { + maVertexCache.push_back(vertex); + ++mnCount; + } + break; + } + + default: + SAL_WARN("canvas.directx", "DXRenderModule::pushVertex(): unexpected primitive type"); + break; + } + } + + + // DXRenderModule::isError + + + bool DXRenderModule::isError() + { + // TODO(P2): get rid of those fine-grained locking + ::osl::MutexGuard aGuard( maMutex ); + + return mbError; + } + + + // DXRenderModule::getAdapterFromWindow + + + UINT DXRenderModule::getAdapterFromWindow() + { + HMONITOR hMonitor(MonitorFromWindow(mhWnd, MONITOR_DEFAULTTONEAREST)); + UINT aAdapterCount(mpDirect3D9->GetAdapterCount()); + for(UINT i=0; i<aAdapterCount; ++i) + if(hMonitor == mpDirect3D9->GetAdapterMonitor(i)) + return i; + return static_cast<UINT>(-1); + } + + + // DXRenderModule::commitVertexCache + + + void DXRenderModule::commitVertexCache() + { + if(maReadIndex != maWriteIndex) + { + const std::size_t nVertexStride = sizeof(dxvertex); + const unsigned int nNumVertices = maWriteIndex-maReadIndex; + const unsigned int nNumPrimitives = nNumVertices / 3; + + if(FAILED(mpDevice->SetStreamSource(0,mpVertexBuffer.get(),0,nVertexStride))) + return; + + if(FAILED(mpDevice->SetFVF(D3DFVF_XYZRHW|D3DFVF_DIFFUSE|D3DFVF_TEX1))) + return; + + if(FAILED(mpDevice->BeginScene())) + return; + + mbError |= FAILED(mpDevice->DrawPrimitive(D3DPT_TRIANGLELIST,maReadIndex,nNumPrimitives)); + mbError |= FAILED(mpDevice->EndScene()); + + maReadIndex += nNumVertices; + } + } + + + // DXRenderModule::flushVertexCache + + + void DXRenderModule::flushVertexCache() + { + if(maVertexCache.empty()) + return; + + mbError=true; + + if( FAILED(mpDevice->SetRenderState(D3DRS_LIGHTING,FALSE))) + return; + + // enable texture alpha blending + if( FAILED(mpDevice->SetRenderState(D3DRS_ALPHABLENDENABLE,TRUE))) + return; + + mpDevice->SetSamplerState(0,D3DSAMP_MAGFILTER,D3DTEXF_LINEAR); + mpDevice->SetSamplerState(0,D3DSAMP_MINFILTER,D3DTEXF_LINEAR); + mpDevice->SetSamplerState(0,D3DSAMP_ADDRESSU ,D3DTADDRESS_CLAMP ); + mpDevice->SetSamplerState(0,D3DSAMP_ADDRESSV ,D3DTADDRESS_CLAMP ); + + // configure the fixed-function pipeline. + // the only 'feature' we need here is to modulate the alpha-channels + // from the texture and the interpolated diffuse color. the result + // will then be blended with the backbuffer. + // fragment color = texture color * diffuse.alpha. + mpDevice->SetTextureStageState(0,D3DTSS_ALPHAOP,D3DTOP_MODULATE); + mpDevice->SetTextureStageState(0,D3DTSS_ALPHAARG1,D3DTA_TEXTURE); + mpDevice->SetTextureStageState(0,D3DTSS_ALPHAARG2,D3DTA_DIFFUSE); + + // normal combination of object... + if( FAILED(mpDevice->SetRenderState(D3DRS_SRCBLEND,D3DBLEND_SRCALPHA)) ) + return; + + // ..and background color + if( FAILED(mpDevice->SetRenderState(D3DRS_DESTBLEND,D3DBLEND_INVSRCALPHA)) ) + return; + + // disable backface culling; this enables us to mirror sprites + // by simply reverting the triangles, which, with enabled + // culling, would be invisible otherwise + if( FAILED(mpDevice->SetRenderState(D3DRS_CULLMODE,D3DCULL_NONE)) ) + return; + + mbError=false; + + std::size_t nSize(maVertexCache.size()); + const std::size_t nVertexStride = sizeof(dxvertex); + + const ::basegfx::B2IVector aPageSize(getPageSize()); + const float nHalfPixelSizeX(0.5f/aPageSize.getX()); + const float nHalfPixelSizeY(0.5f/aPageSize.getY()); + vertexCache_t::const_iterator it(maVertexCache.begin()); + + while( nSize ) + { + DWORD dwLockFlags(D3DLOCK_NOOVERWRITE); + + // Check to see if there's space for the current set of + // vertices in the buffer. + if( maNumVertices - maWriteIndex < nSize ) + { + commitVertexCache(); + dwLockFlags = D3DLOCK_DISCARD; + maWriteIndex = 0; + maReadIndex = 0; + } + + dxvertex *vertices(nullptr); + const std::size_t nNumVertices( + std::min(maNumVertices - maWriteIndex, + nSize)); + if(FAILED(mpVertexBuffer->Lock(maWriteIndex*nVertexStride, + nNumVertices*nVertexStride, + reinterpret_cast<void **>(&vertices), + dwLockFlags))) + return; + + std::size_t nIndex(0); + while( nIndex < nNumVertices ) + { + dxvertex &dest = vertices[nIndex++]; + dest.x=it->x; + dest.y=it->y; + dest.z=it->z; + dest.rhw=1; + const sal_uInt32 alpha(static_cast<sal_uInt32>(it->a*255.0f)); + dest.diffuse=D3DCOLOR_ARGB(alpha,255,255,255); + dest.u=static_cast<float>(it->u + nHalfPixelSizeX); + dest.v=static_cast<float>(it->v + nHalfPixelSizeY); + ++it; + } + + mpVertexBuffer->Unlock(); + + // Advance to the next position in the vertex buffer. + maWriteIndex += nNumVertices; + nSize -= nNumVertices; + + commitVertexCache(); + } + + maVertexCache.clear(); + } + } + + + // createRenderModule + + + IDXRenderModuleSharedPtr createRenderModule( const vcl::Window& rParent ) + { + return std::make_shared<DXRenderModule>(rParent); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_bitmap.cxx b/canvas/source/directx/dx_bitmap.cxx new file mode 100644 index 0000000000..9e5f2348fa --- /dev/null +++ b/canvas/source/directx/dx_bitmap.cxx @@ -0,0 +1,204 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <memory> + +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/range/b2irange.hxx> +#include <comphelper/diagnose_ex.hxx> + +#include "dx_bitmap.hxx" +#include "dx_graphicsprovider.hxx" +#include "dx_impltools.hxx" + +using namespace ::com::sun::star; + +namespace dxcanvas +{ + + // DXBitmap::DXBitmap + + + DXBitmap::DXBitmap( const BitmapSharedPtr& rBitmap, + bool bWithAlpha ) : + mpGdiPlusUser( GDIPlusUser::createInstance() ), + maSize(rBitmap->GetWidth(),rBitmap->GetHeight()), + mpBitmap(rBitmap), + mpGraphics(tools::createGraphicsFromBitmap(mpBitmap)), + mbAlpha(bWithAlpha) + { + } + + DXBitmap::DXBitmap( const ::basegfx::B2ISize& rSize, + bool bWithAlpha ) : + mpGdiPlusUser( GDIPlusUser::createInstance() ), + maSize(rSize), + mpBitmap(), + mpGraphics(), + mbAlpha(bWithAlpha) + { + // create container for pixel data + if(mbAlpha) + { + mpBitmap = std::make_shared<Gdiplus::Bitmap>( + maSize.getWidth(), + maSize.getHeight(), + PixelFormat32bppARGB); + } + else + { + mpBitmap = std::make_shared<Gdiplus::Bitmap>( + maSize.getWidth(), + maSize.getHeight(), + PixelFormat24bppRGB); + } + + mpGraphics = tools::createGraphicsFromBitmap(mpBitmap); + } + + BitmapSharedPtr DXBitmap::getBitmap() const + { + return mpBitmap; + } + + GraphicsSharedPtr DXBitmap::getGraphics() + { + return mpGraphics; + } + + ::basegfx::B2ISize DXBitmap::getSize() const + { + return maSize; + } + + bool DXBitmap::hasAlpha() const + { + return mbAlpha; + } + + uno::Sequence< sal_Int8 > DXBitmap::getData( rendering::IntegerBitmapLayout& /*bitmapLayout*/, + const geometry::IntegerRectangle2D& rect ) + { + uno::Sequence< sal_Int8 > aRes( (rect.X2-rect.X1)*(rect.Y2-rect.Y1)*4 ); // TODO(F1): Be format-agnostic here + + const Gdiplus::Rect aRect( tools::gdiPlusRectFromIntegerRectangle2D( rect ) ); + + Gdiplus::BitmapData aBmpData; + aBmpData.Width = rect.X2-rect.X1; + aBmpData.Height = rect.Y2-rect.Y1; + aBmpData.Stride = 4*aBmpData.Width; + aBmpData.PixelFormat = PixelFormat32bppARGB; + aBmpData.Scan0 = aRes.getArray(); + + // TODO(F1): Support more pixel formats natively + + // read data from bitmap + if( Gdiplus::Ok != mpBitmap->LockBits( &aRect, + Gdiplus::ImageLockModeRead | Gdiplus::ImageLockModeUserInputBuf, + PixelFormat32bppARGB, // TODO(F1): Adapt to + // Graphics native + // format/change + // getMemoryLayout + &aBmpData ) ) + { + // failed to lock, bail out + return uno::Sequence< sal_Int8 >(); + } + + mpBitmap->UnlockBits( &aBmpData ); + + return aRes; + } + + void DXBitmap::setData( const uno::Sequence< sal_Int8 >& data, + const rendering::IntegerBitmapLayout& /*bitmapLayout*/, + const geometry::IntegerRectangle2D& rect ) + { + const Gdiplus::Rect aRect( tools::gdiPlusRectFromIntegerRectangle2D( rect ) ); + + Gdiplus::BitmapData aBmpData; + aBmpData.Width = rect.X2-rect.X1; + aBmpData.Height = rect.Y2-rect.Y1; + aBmpData.Stride = 4*aBmpData.Width; + aBmpData.PixelFormat = PixelFormat32bppARGB; + aBmpData.Scan0 = const_cast<sal_Int8 *>(data.getConstArray()); + // const_cast is safe, "Gdiplus::ImageLockModeWrite + // | Gdiplus::ImageLockModeUserInputBuf makes the data go from + // BitmapData into Bitmap", says Thorsten + + // TODO(F1): Support more pixel formats natively + + if( Gdiplus::Ok != mpBitmap->LockBits( &aRect, + Gdiplus::ImageLockModeWrite | Gdiplus::ImageLockModeUserInputBuf, + PixelFormat32bppARGB, // TODO: Adapt to + // Graphics native + // format/change + // getMemoryLayout + &aBmpData ) ) + { + throw uno::RuntimeException("Internal error while writing BitmapData into Bitmap"); + } + + // commit data to bitmap + mpBitmap->UnlockBits( &aBmpData ); + } + + void DXBitmap::setPixel( const uno::Sequence< sal_Int8 >& color, + const rendering::IntegerBitmapLayout& /*bitmapLayout*/, + const geometry::IntegerPoint2D& pos ) + { + const geometry::IntegerSize2D aSize( maSize.getWidth(),maSize.getHeight() ); + + ENSURE_ARG_OR_THROW( pos.X >= 0 && pos.X < aSize.Width, + "CanvasHelper::setPixel: X coordinate out of bounds" ); + ENSURE_ARG_OR_THROW( pos.Y >= 0 && pos.Y < aSize.Height, + "CanvasHelper::setPixel: Y coordinate out of bounds" ); + ENSURE_ARG_OR_THROW( color.getLength() > 3, + "CanvasHelper::setPixel: not enough color components" ); + + if( Gdiplus::Ok != mpBitmap->SetPixel( pos.X, pos.Y, + Gdiplus::Color( tools::sequenceToArgb( color )))) + { + throw uno::RuntimeException("SetPixel called with invalid x,y points or color"); + } + } + + uno::Sequence< sal_Int8 > DXBitmap::getPixel( rendering::IntegerBitmapLayout& /*bitmapLayout*/, + const geometry::IntegerPoint2D& pos ) + { + const geometry::IntegerSize2D aSize( maSize.getWidth(),maSize.getHeight() ); + + ENSURE_ARG_OR_THROW( pos.X >= 0 && pos.X < aSize.Width, + "CanvasHelper::getPixel: X coordinate out of bounds" ); + ENSURE_ARG_OR_THROW( pos.Y >= 0 && pos.Y < aSize.Height, + "CanvasHelper::getPixel: Y coordinate out of bounds" ); + + Gdiplus::Color aColor; + + if( Gdiplus::Ok != mpBitmap->GetPixel( pos.X, pos.Y, &aColor ) ) + return uno::Sequence< sal_Int8 >(); + + return tools::argbToIntSequence(aColor.GetValue()); + } + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_bitmap.hxx b/canvas/source/directx/dx_bitmap.hxx new file mode 100644 index 0000000000..b27da5cfa6 --- /dev/null +++ b/canvas/source/directx/dx_bitmap.hxx @@ -0,0 +1,82 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/rendering/XCanvas.hpp> +#include <com/sun/star/rendering/XIntegerBitmap.hpp> +#include <basegfx/vector/b2ivector.hxx> +#include <basegfx/point/b2dpoint.hxx> +#include <basegfx/range/b2drange.hxx> +#include <memory> +#include "dx_winstuff.hxx" +#include "dx_ibitmap.hxx" +#include "dx_graphicsprovider.hxx" +#include "dx_gdiplususer.hxx" + +namespace dxcanvas +{ + class DXBitmap : public IBitmap + { + public: + DXBitmap( const BitmapSharedPtr& rBitmap, bool bWithAlpha ); + DXBitmap( const ::basegfx::B2ISize& rSize, bool bWithAlpha ); + + virtual GraphicsSharedPtr getGraphics() override; + + virtual BitmapSharedPtr getBitmap() const override; + virtual ::basegfx::B2ISize getSize() const override; + virtual bool hasAlpha() const override; + + css::uno::Sequence< sal_Int8 > getData( + css::rendering::IntegerBitmapLayout& bitmapLayout, + const css::geometry::IntegerRectangle2D& rect ) override; + + void setData( + const css::uno::Sequence< sal_Int8 >& data, + const css::rendering::IntegerBitmapLayout& bitmapLayout, + const css::geometry::IntegerRectangle2D& rect ) override; + + void setPixel( + const css::uno::Sequence< sal_Int8 >& color, + const css::rendering::IntegerBitmapLayout& bitmapLayout, + const css::geometry::IntegerPoint2D& pos ) override; + + css::uno::Sequence< sal_Int8 > getPixel( + css::rendering::IntegerBitmapLayout& bitmapLayout, + const css::geometry::IntegerPoint2D& pos ) override; + + private: + // Refcounted global GDI+ state container + GDIPlusUserSharedPtr mpGdiPlusUser; + + // size of this image in pixels [integral unit] + ::basegfx::B2ISize maSize; + + BitmapSharedPtr mpBitmap; + GraphicsSharedPtr mpGraphics; + + // true if the bitmap contains an alpha channel + bool mbAlpha; + }; + + typedef std::shared_ptr< DXBitmap > DXBitmapSharedPtr; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_bitmapcanvashelper.cxx b/canvas/source/directx/dx_bitmapcanvashelper.cxx new file mode 100644 index 0000000000..f82fa0ac3a --- /dev/null +++ b/canvas/source/directx/dx_bitmapcanvashelper.cxx @@ -0,0 +1,221 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> +#include <sal/log.hxx> + +#include <algorithm> + +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/point/b2dpoint.hxx> +#include <basegfx/utils/canvastools.hxx> +#include <com/sun/star/rendering/CompositeOperation.hpp> +#include <com/sun/star/rendering/PathCapType.hpp> +#include <com/sun/star/rendering/PathJoinType.hpp> +#include <com/sun/star/rendering/RepaintResult.hpp> +#include <com/sun/star/rendering/TexturingMode.hpp> +#include <rtl/math.hxx> +#include <comphelper/diagnose_ex.hxx> + +#include <canvas/canvastools.hxx> + +#include "dx_bitmapcanvashelper.hxx" +#include "dx_canvasfont.hxx" +#include "dx_impltools.hxx" +#include "dx_spritecanvas.hxx" +#include "dx_textlayout.hxx" + + +using namespace ::com::sun::star; + +namespace dxcanvas +{ + BitmapCanvasHelper::BitmapCanvasHelper() : + mpTarget() + {} + + void BitmapCanvasHelper::disposing() + { + mpTarget.reset(); + CanvasHelper::disposing(); + } + + void BitmapCanvasHelper::setTarget( const IBitmapSharedPtr& rTarget ) + { + ENSURE_OR_THROW( rTarget, + "BitmapCanvasHelper::setTarget(): Invalid target" ); + ENSURE_OR_THROW( !mpTarget, + "BitmapCanvasHelper::setTarget(): target set, old target would be overwritten" ); + + mpTarget = rTarget; + CanvasHelper::setTarget(rTarget); + } + + void BitmapCanvasHelper::setTarget( const IBitmapSharedPtr& rTarget, + const ::basegfx::B2ISize& rOutputOffset ) + { + ENSURE_OR_THROW( rTarget, + "BitmapCanvasHelper::setTarget(): invalid target" ); + ENSURE_OR_THROW( !mpTarget, + "BitmapCanvasHelper::setTarget(): target set, old target would be overwritten" ); + + mpTarget = rTarget; + CanvasHelper::setTarget(rTarget,rOutputOffset); + } + + void BitmapCanvasHelper::clear() + { + if( needOutput() ) + { + GraphicsSharedPtr pGraphics( mpTarget->getGraphics() ); + + Gdiplus::Color aClearColor = hasAlpha() ? + Gdiplus::Color( 0,255,255,255 ) : Gdiplus::Color(Gdiplus::ARGB(Gdiplus::Color::White)); + + ENSURE_OR_THROW( + Gdiplus::Ok == pGraphics->SetCompositingMode( + Gdiplus::CompositingModeSourceCopy ), // force set, don't blend + "BitmapCanvasHelper::clear(): GDI+ SetCompositingMode call failed" ); + ENSURE_OR_THROW( + Gdiplus::Ok == pGraphics->Clear( aClearColor ), + "BitmapCanvasHelper::clear(): GDI+ Clear call failed" ); + } + } + + uno::Reference< rendering::XCachedPrimitive > BitmapCanvasHelper::drawTextLayout( const rendering::XCanvas* /*pCanvas*/, + const uno::Reference< rendering::XTextLayout >& xLayoutetText, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState ) + { + ENSURE_OR_THROW( xLayoutetText.is(), + "BitmapCanvasHelper::drawTextLayout: layout is NULL"); + + if( needOutput() ) + { + TextLayout* pTextLayout = + dynamic_cast< TextLayout* >( xLayoutetText.get() ); + + ENSURE_OR_THROW( pTextLayout, + "BitmapCanvasHelper::drawTextLayout(): TextLayout not compatible with this canvas" ); + + pTextLayout->draw( mpTarget->getGraphics(), + viewState, + renderState, + maOutputOffset, + mpDevice, + mpTarget->hasAlpha() ); + } + + return uno::Reference< rendering::XCachedPrimitive >(nullptr); + } + + void BitmapCanvasHelper::copyRect( const rendering::XCanvas* /*pCanvas*/, + const uno::Reference< rendering::XBitmapCanvas >& /*sourceCanvas*/, + const geometry::RealRectangle2D& /*sourceRect*/, + const rendering::ViewState& /*sourceViewState*/, + const rendering::RenderState& /*sourceRenderState*/, + const geometry::RealRectangle2D& /*destRect*/, + const rendering::ViewState& /*destViewState*/, + const rendering::RenderState& /*destRenderState*/ ) + { + // TODO(F2): copyRect NYI + } + + geometry::IntegerSize2D BitmapCanvasHelper::getSize() + { + if( !mpTarget ) + return geometry::IntegerSize2D(1, 1); + return basegfx::unotools::integerSize2DFromB2ISize(mpTarget->getSize()); + } + + uno::Reference< rendering::XBitmap > BitmapCanvasHelper::getScaledBitmap( const geometry::RealSize2D& /*newSize*/, + bool /*beFast*/ ) + { + // TODO(F1): + return uno::Reference< rendering::XBitmap >(); + } + + uno::Sequence< sal_Int8 > BitmapCanvasHelper::getData( rendering::IntegerBitmapLayout& bitmapLayout, + const geometry::IntegerRectangle2D& rect ) + { + SAL_INFO( "canvas.directx", "::dxcanvas::BitmapCanvasHelper::getData()" ); + + ENSURE_OR_THROW( mpTarget, + "::dxcanvas::BitmapCanvasHelper::getData(): disposed" ); + + bitmapLayout = getMemoryLayout(); + return mpTarget->getData(bitmapLayout,rect); + } + + void BitmapCanvasHelper::setData( const uno::Sequence< sal_Int8 >& data, + const rendering::IntegerBitmapLayout& bitmapLayout, + const geometry::IntegerRectangle2D& rect ) + { + SAL_INFO( "canvas.directx", "::dxcanvas::BitmapCanvasHelper::setData()" ); + + ENSURE_OR_THROW( mpTarget, + "::dxcanvas::BitmapCanvasHelper::setData(): disposed" ); + + mpTarget->setData(data,bitmapLayout,rect); + } + + void BitmapCanvasHelper::setPixel( const uno::Sequence< sal_Int8 >& color, + const rendering::IntegerBitmapLayout& bitmapLayout, + const geometry::IntegerPoint2D& pos ) + { + SAL_INFO( "canvas.directx", "::dxcanvas::BitmapCanvasHelper::setPixel()" ); + + ENSURE_OR_THROW( mpTarget, + "::dxcanvas::BitmapCanvasHelper::setPixel(): disposed" ); + + mpTarget->setPixel(color,bitmapLayout,pos); + } + + uno::Sequence< sal_Int8 > BitmapCanvasHelper::getPixel( rendering::IntegerBitmapLayout& bitmapLayout, + const geometry::IntegerPoint2D& pos ) + { + SAL_INFO( "canvas.directx", "::dxcanvas::BitmapCanvasHelper::getPixel()" ); + + ENSURE_OR_THROW( mpTarget, + "::dxcanvas::BitmapCanvasHelper::getPixel(): disposed" ); + + bitmapLayout = getMemoryLayout(); + return mpTarget->getPixel(bitmapLayout,pos); + } + + uno::Reference< rendering::XBitmapPalette > BitmapCanvasHelper::getPalette() + { + // TODO(F1): Palette bitmaps NYI + return uno::Reference< rendering::XBitmapPalette >(); + } + + rendering::IntegerBitmapLayout BitmapCanvasHelper::getMemoryLayout() + { + if( !mpTarget ) + return rendering::IntegerBitmapLayout(); // we're disposed + + return ::canvas::tools::getStdMemoryLayout(getSize()); + } + bool BitmapCanvasHelper::hasAlpha() const + { + return mpTarget && mpTarget->hasAlpha(); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_bitmapcanvashelper.hxx b/canvas/source/directx/dx_bitmapcanvashelper.hxx new file mode 100644 index 0000000000..46f970493f --- /dev/null +++ b/canvas/source/directx/dx_bitmapcanvashelper.hxx @@ -0,0 +1,126 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/rendering/XCanvas.hpp> +#include <com/sun/star/rendering/XBitmapCanvas.hpp> + +#include <basegfx/vector/b2isize.hxx> +#include <basegfx/vector/b2dsize.hxx> + +#include "dx_graphicsprovider.hxx" +#include "dx_ibitmap.hxx" +#include "dx_gdiplususer.hxx" +#include "dx_impltools.hxx" +#include "dx_canvashelper.hxx" + + +namespace dxcanvas +{ + /** Helper class for basic canvas functionality. Also offers + optional backbuffer painting, when providing it with a second + HDC to render into. + */ + class BitmapCanvasHelper : public CanvasHelper + { + public: + BitmapCanvasHelper(); + + /// Release all references + void disposing(); + + /** Set the target for rendering operations + + @param rTarget + Render target + */ + void setTarget( const IBitmapSharedPtr& rTarget ); + + /** Set the target for rendering operations + + @param rTarget + Render target + + @param rOutputOffset + Output offset in pixel + */ + void setTarget( const IBitmapSharedPtr& rTarget, + const ::basegfx::B2ISize& rOutputOffset ); + + + // CanvasHelper functionality is implementation-inherited. yuck. + // ============================================================= + void clear(); + + css::uno::Reference< css::rendering::XCachedPrimitive > + drawTextLayout( const css::rendering::XCanvas* pCanvas, + const css::uno::Reference< + css::rendering::XTextLayout >& laidOutText, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState ); + + // BitmapCanvasHelper functionality + // ================================ + + void copyRect( const css::rendering::XCanvas* pCanvas, + const css::uno::Reference< + css::rendering::XBitmapCanvas >& sourceCanvas, + const css::geometry::RealRectangle2D& sourceRect, + const css::rendering::ViewState& sourceViewState, + const css::rendering::RenderState& sourceRenderState, + const css::geometry::RealRectangle2D& destRect, + const css::rendering::ViewState& destViewState, + const css::rendering::RenderState& destRenderState ); + + css::geometry::IntegerSize2D getSize(); + + css::uno::Reference< css::rendering::XBitmap > + getScaledBitmap( const css::geometry::RealSize2D& newSize, + bool beFast ); + + css::uno::Sequence< sal_Int8 > + getData( css::rendering::IntegerBitmapLayout& bitmapLayout, + const css::geometry::IntegerRectangle2D& rect ); + + void setData( const css::uno::Sequence< sal_Int8 >& data, + const css::rendering::IntegerBitmapLayout& bitmapLayout, + const css::geometry::IntegerRectangle2D& rect ); + + void setPixel( const css::uno::Sequence< sal_Int8 >& color, + const css::rendering::IntegerBitmapLayout& bitmapLayout, + const css::geometry::IntegerPoint2D& pos ); + + css::uno::Sequence< sal_Int8 > + getPixel( css::rendering::IntegerBitmapLayout& bitmapLayout, + const css::geometry::IntegerPoint2D& pos ); + + css::uno::Reference< css::rendering::XBitmapPalette > getPalette(); + + css::rendering::IntegerBitmapLayout getMemoryLayout(); + + bool hasAlpha() const; + + protected: + /// Render target + IBitmapSharedPtr mpTarget; + }; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_bitmapprovider.hxx b/canvas/source/directx/dx_bitmapprovider.hxx new file mode 100644 index 0000000000..ad83abee0f --- /dev/null +++ b/canvas/source/directx/dx_bitmapprovider.hxx @@ -0,0 +1,34 @@ +/* -*- 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 . + */ + +#pragma once + +#include "dx_ibitmap.hxx" +#include <memory> + +namespace dxcanvas +{ +struct BitmapProvider +{ + virtual ~BitmapProvider() {} + virtual IBitmapSharedPtr getBitmap() const = 0; +}; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_canvas.cxx b/canvas/source/directx/dx_canvas.cxx new file mode 100644 index 0000000000..c19ea1ea90 --- /dev/null +++ b/canvas/source/directx/dx_canvas.cxx @@ -0,0 +1,256 @@ +// /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <memory> +#include <utility> + +#include <sal/log.hxx> + +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/numeric/ftools.hxx> +#include <basegfx/point/b2dpoint.hxx> +#include <basegfx/utils/canvastools.hxx> +#include <com/sun/star/awt/XSystemDependentWindowPeer.hpp> +#include <com/sun/star/awt/XWindow.hpp> +#include <com/sun/star/lang/NoSupportException.hpp> +#include <com/sun/star/lang/XSingleServiceFactory.hpp> +#include <com/sun/star/registry/XRegistryKey.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <cppuhelper/supportsservice.hxx> +#include <osl/mutex.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <vcl/sysdata.hxx> +#include <vcl/skia/SkiaHelper.hxx> +#include <vcl/window.hxx> + +#include <canvas/canvastools.hxx> + +#include "dx_canvas.hxx" +#include "dx_graphicsprovider.hxx" +#include "dx_winstuff.hxx" + +using namespace ::com::sun::star; + +namespace dxcanvas +{ + namespace { + + /// Actual canonical implementation of the GraphicsProvider interface + class GraphicsProviderImpl : public GraphicsProvider + { + GraphicsSharedPtr mpGraphics; + public: + explicit GraphicsProviderImpl( GraphicsSharedPtr && pGraphics ) : mpGraphics( std::move(pGraphics) ) {} + virtual GraphicsSharedPtr getGraphics() override { return mpGraphics; } + }; + + } + + Canvas::Canvas( const uno::Sequence< uno::Any >& aArguments, + const uno::Reference< uno::XComponentContext >& rxContext ) : + maArguments(aArguments), + mxComponentContext( rxContext ) + { + } + + void Canvas::initialize() + { + // #i64742# Only perform initialization when not in probe mode + if( maArguments.getLength() == 0 ) + return; + + assert( !SkiaHelper::isVCLSkiaEnabled() ); + + SAL_INFO("canvas.directx", "Canvas::initialize called" ); + + // At index 1, we expect a HWND handle here, containing a + // pointer to a valid window, on which to output + // At index 2, we expect the current window bound rect + ENSURE_ARG_OR_THROW( maArguments.getLength() >= 5 && + maArguments[4].getValueTypeClass() == uno::TypeClass_SEQUENCE, + "Canvas::initialize: wrong number of arguments, or wrong types" ); + + uno::Sequence<sal_Int8> aSeq; + maArguments[4] >>= aSeq; + + const SystemGraphicsData* pSysData=reinterpret_cast<const SystemGraphicsData*>(aSeq.getConstArray()); + if( !pSysData || !pSysData->hDC ) + throw lang::NoSupportException("Passed SystemGraphicsData or HDC invalid!"); + + sal_Int64 nPtr = 0; + maArguments[0] >>= nPtr; + OutputDevice* pOutDev = reinterpret_cast<OutputDevice*>(nPtr); + ENSURE_ARG_OR_THROW( pOutDev != nullptr,"Canvas::initialize: invalid OutDev pointer" ); + + // setup helper + maDeviceHelper.init( pSysData->hDC, pOutDev, *this ); + maCanvasHelper.setDevice( *this ); + maCanvasHelper.setTarget( + std::make_shared<GraphicsProviderImpl>( + GraphicsSharedPtr(Gdiplus::Graphics::FromHDC(pSysData->hDC)))); + + maArguments.realloc(0); + } + + void Canvas::disposeThis() + { + ::osl::MutexGuard aGuard( m_aMutex ); + + mxComponentContext.clear(); + + // forward to parent + CanvasBaseT::disposeThis(); + } + + OUString SAL_CALL Canvas::getServiceName( ) + { + return "com.sun.star.rendering.Canvas.GDI+"; + } + + // XServiceInfo + css::uno::Sequence<OUString> Canvas::getSupportedServiceNames( ) + { + return { "com.sun.star.rendering.Canvas.GDI+" }; + } + OUString Canvas::getImplementationName( ) + { + return "com.sun.star.comp.rendering.Canvas.GDI+"; + } + sal_Bool Canvas::supportsService( const OUString& sServiceName ) + { + return cppu::supportsService(this, sServiceName); + } + + BitmapCanvas::BitmapCanvas( const uno::Sequence< uno::Any >& aArguments, + const uno::Reference< uno::XComponentContext >& rxContext ) : + maArguments(aArguments), + mxComponentContext( rxContext ), + mpTarget() + { + } + + void BitmapCanvas::initialize() + { + // #i64742# Only perform initialization when not in probe mode + if( maArguments.getLength() == 0 ) + return; + + SAL_INFO("canvas.directx", "BitmapCanvas::initialize called" ); + + // At index 1, we expect a HWND handle here, containing a + // pointer to a valid window, on which to output + // At index 2, we expect the current window bound rect + ENSURE_ARG_OR_THROW( maArguments.getLength() >= 5 && + maArguments[4].getValueTypeClass() == uno::TypeClass_SEQUENCE, + "Canvas::initialize: wrong number of arguments, or wrong types" ); + + uno::Sequence<sal_Int8> aSeq; + maArguments[4] >>= aSeq; + + const SystemGraphicsData* pSysData=reinterpret_cast<const SystemGraphicsData*>(aSeq.getConstArray()); + if( !pSysData || !pSysData->hDC ) + throw lang::NoSupportException( "Passed SystemGraphicsData or HDC invalid!"); + + sal_Int64 nPtr = 0; + maArguments[0] >>= nPtr; + OutputDevice* pOutDev = reinterpret_cast<OutputDevice*>(nPtr); + ENSURE_ARG_OR_THROW( pOutDev != nullptr,"Canvas::initialize: invalid OutDev pointer" ); + + // setup helper + maDeviceHelper.init( pSysData->hDC, pOutDev, *this ); + maCanvasHelper.setDevice( *this ); + + // check whether we can actually provide a BitmapCanvas + // here. for this, check whether the HDC has a bitmap + // selected. + HBITMAP hBmp; + hBmp=static_cast<HBITMAP>(GetCurrentObject(pSysData->hDC, OBJ_BITMAP)); + if( !hBmp || GetObjectType(pSysData->hDC) != OBJ_MEMDC ) + { + throw lang::NoSupportException( "Passed HDC is no mem DC/has no bitmap selected!"); + } + + mpTarget = std::make_shared<DXBitmap>( + BitmapSharedPtr( + Gdiplus::Bitmap::FromHBITMAP( + hBmp, nullptr) ), + false ); + + maCanvasHelper.setTarget( mpTarget ); + + maArguments.realloc(0); + } + + void BitmapCanvas::disposeThis() + { + ::osl::MutexGuard aGuard( m_aMutex ); + + mpTarget.reset(); + mxComponentContext.clear(); + + // forward to parent + BitmapCanvasBaseT::disposeThis(); + } + + OUString SAL_CALL BitmapCanvas::getServiceName( ) + { + return "com.sun.star.rendering.BitmapCanvas.GDI+"; + } + + // XServiceInfo + css::uno::Sequence<OUString> BitmapCanvas::getSupportedServiceNames( ) + { + return { "com.sun.star.rendering.BitmapCanvas.GDI+" }; + } + OUString BitmapCanvas::getImplementationName( ) + { + return "com.sun.star.comp.rendering.BitmapCanvas.GDI+"; + } + sal_Bool BitmapCanvas::supportsService( const OUString& sServiceName ) + { + return cppu::supportsService(this, sServiceName); + } + + IBitmapSharedPtr BitmapCanvas::getBitmap() const + { + return mpTarget; + } + + extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* + canvas_gdiplus_Canvas_get_implementation( + css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const& args) + { + rtl::Reference<Canvas> xCanvas(new Canvas(args, context)); + xCanvas->initialize(); + return cppu::acquire(xCanvas.get()); + } + + extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* + canvas_gdiplus_BitmapCanvas_get_implementation( + css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const& args) + { + rtl::Reference<BitmapCanvas> xCanvas(new BitmapCanvas(args, context)); + xCanvas->initialize(); + return cppu::acquire(xCanvas.get()); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_canvas.hxx b/canvas/source/directx/dx_canvas.hxx new file mode 100644 index 0000000000..5a00e07fa4 --- /dev/null +++ b/canvas/source/directx/dx_canvas.hxx @@ -0,0 +1,173 @@ +/* -*- 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 . + */ + +#pragma once + +#include <rtl/ref.hxx> + +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/lang/XServiceName.hpp> +#include <com/sun/star/util/XUpdatable.hpp> +#include <com/sun/star/rendering/XBitmapCanvas.hpp> +#include <com/sun/star/rendering/XIntegerBitmap.hpp> +#include <com/sun/star/rendering/XGraphicDevice.hpp> +#include <com/sun/star/rendering/XBufferController.hpp> + +#include <cppuhelper/compbase.hxx> +#include <comphelper/uno3.hxx> + +#include <base/integerbitmapbase.hxx> +#include <base/basemutexhelper.hxx> +#include <base/graphicdevicebase.hxx> +#include <base/canvasbase.hxx> +#include <base/bitmapcanvasbase.hxx> + +#include "dx_bitmapprovider.hxx" +#include "dx_canvashelper.hxx" +#include "dx_bitmapcanvashelper.hxx" +#include "dx_impltools.hxx" +#include "dx_devicehelper.hxx" + + +namespace dxcanvas +{ + typedef ::cppu::WeakComponentImplHelper< css::rendering::XCanvas, + css::rendering::XGraphicDevice, + css::lang::XMultiServiceFactory, + css::util::XUpdatable, + css::beans::XPropertySet, + css::lang::XServiceName, + css::lang::XServiceInfo> GraphicDeviceBase1_Base; + typedef ::canvas::GraphicDeviceBase< ::canvas::BaseMutexHelper< GraphicDeviceBase1_Base >, + DeviceHelper, + ::osl::MutexGuard, + ::cppu::OWeakObject > CanvasBase1_Base; + typedef ::canvas::CanvasBase< CanvasBase1_Base, + CanvasHelper, + ::osl::MutexGuard, + ::cppu::OWeakObject > CanvasBaseT; + + /** Product of this component's factory. + + The Canvas object combines the actual Window canvas with + the XGraphicDevice interface. This is because there's a + one-to-one relation between them, anyway, since each window + can have exactly one canvas and one associated + XGraphicDevice. And to avoid messing around with circular + references, this is implemented as one single object. + */ + class Canvas : public CanvasBaseT + { + public: + Canvas( const css::uno::Sequence< + css::uno::Any >& aArguments, + const css::uno::Reference< + css::uno::XComponentContext >& rxContext ); + + void initialize(); + + /// Dispose all internal references + virtual void disposeThis() override; + + // Forwarding the XComponent implementation to the + // cppu::ImplHelper templated base + // Classname Base doing refcounting Base implementing the XComponent interface + // | | | + // V V V + DECLARE_UNO3_XCOMPONENT_AGG_DEFAULTS( Canvas, GraphicDeviceBase1_Base, ::cppu::WeakComponentImplHelperBase ) + + // XServiceName + virtual OUString SAL_CALL getServiceName( ) override; + + // XServiceInfo + virtual css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames( ) override; + virtual OUString SAL_CALL getImplementationName( ) override; + virtual sal_Bool SAL_CALL supportsService( const OUString& ) override; + + private: + css::uno::Sequence< css::uno::Any > maArguments; + css::uno::Reference< css::uno::XComponentContext > mxComponentContext; + }; + + typedef ::cppu::WeakComponentImplHelper< css::rendering::XBitmapCanvas, + css::rendering::XIntegerBitmap, + css::rendering::XGraphicDevice, + css::lang::XMultiServiceFactory, + css::util::XUpdatable, + css::beans::XPropertySet, + css::lang::XServiceName, + css::lang::XServiceInfo > GraphicDeviceBase2_Base; + typedef ::canvas::GraphicDeviceBase< ::canvas::BaseMutexHelper< GraphicDeviceBase2_Base >, + DeviceHelper, + ::osl::MutexGuard, + ::cppu::OWeakObject > CanvasBase2_Base; + typedef ::canvas::IntegerBitmapBase< + canvas::BitmapCanvasBase2< + CanvasBase2_Base, + BitmapCanvasHelper, + ::osl::MutexGuard, + ::cppu::OWeakObject> > BitmapCanvasBaseT; + + /** Product of this component's factory. + + The Canvas object combines the actual Window canvas with + the XGraphicDevice interface. This is because there's a + one-to-one relation between them, anyway, since each window + can have exactly one canvas and one associated + XGraphicDevice. And to avoid messing around with circular + references, this is implemented as one single object. + */ + class BitmapCanvas : public BitmapCanvasBaseT, public BitmapProvider + { + public: + BitmapCanvas( const css::uno::Sequence< css::uno::Any >& aArguments, + const css::uno::Reference< css::uno::XComponentContext >& rxContext ); + + void initialize(); + + /// Dispose all internal references + virtual void disposeThis() override; + + // Forwarding the XComponent implementation to the + // cppu::ImplHelper templated base + // Classname Base doing refcounting Base implementing the XComponent interface + // | | | + // V V V + DECLARE_UNO3_XCOMPONENT_AGG_DEFAULTS( BitmapCanvas, GraphicDeviceBase2_Base, ::cppu::WeakComponentImplHelperBase ) + + // XServiceName + virtual OUString SAL_CALL getServiceName( ) override; + + // XServiceInfo + virtual css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames( ) override; + virtual OUString SAL_CALL getImplementationName( ) override; + virtual sal_Bool SAL_CALL supportsService( const OUString& ) override; + + // BitmapProvider + virtual IBitmapSharedPtr getBitmap() const override; + + private: + css::uno::Sequence< css::uno::Any > maArguments; + css::uno::Reference< css::uno::XComponentContext > mxComponentContext; + IBitmapSharedPtr mpTarget; + }; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_canvasbitmap.cxx b/canvas/source/directx/dx_canvasbitmap.cxx new file mode 100644 index 0000000000..fb06288ada --- /dev/null +++ b/canvas/source/directx/dx_canvasbitmap.cxx @@ -0,0 +1,256 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <memory> + +#include <cppuhelper/supportsservice.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <vcl/bitmapex.hxx> + +#include <canvas/canvastools.hxx> + +#include "dx_canvasbitmap.hxx" +#include "dx_impltools.hxx" + + +using namespace ::com::sun::star; + +namespace dxcanvas +{ + CanvasBitmap::CanvasBitmap( const IBitmapSharedPtr& rBitmap, + const DeviceRef& rDevice ) : + mpDevice( rDevice ), + mpBitmap( rBitmap ) + { + ENSURE_OR_THROW( mpDevice.is() && mpBitmap, + "CanvasBitmap::CanvasBitmap(): Invalid surface or device" ); + + maCanvasHelper.setDevice( *mpDevice ); + maCanvasHelper.setTarget( mpBitmap ); + } + + void CanvasBitmap::disposeThis() + { + mpBitmap.reset(); + mpDevice.clear(); + + // forward to parent + CanvasBitmap_Base::disposeThis(); + } + + namespace { + + struct AlphaDIB + { + BITMAPINFOHEADER bmiHeader; + RGBQUAD bmiColors[256]; + AlphaDIB() + : bmiHeader({0,0,0,1,8,BI_RGB,0,0,0,0,0}) + { + for (size_t i = 0; i < 256; ++i) + { + // this here fills palette with grey level colors, starting + // from 0,0,0 up to 255,255,255 + BYTE const b(i); + bmiColors[i] = { b,b,b,b }; + } + } + }; + + } + + uno::Any SAL_CALL CanvasBitmap::getFastPropertyValue( sal_Int32 nHandle ) + { + uno::Any aRes; + // 0 ... get BitmapEx + // 1 ... get Pixbuf with bitmap RGB content + // 2 ... get Pixbuf with bitmap alpha mask + switch( nHandle ) + { + // sorry, no BitmapEx here... + case 0: + aRes <<= reinterpret_cast<sal_Int64>( nullptr ); + break; + + case 1: + { + if(!mpBitmap->hasAlpha()) + { + HBITMAP aHBmp; + mpBitmap->getBitmap()->GetHBITMAP(Gdiplus::Color(), &aHBmp ); + + uno::Sequence< uno::Any > args{ uno::Any(reinterpret_cast<sal_Int64>(aHBmp)) }; + aRes <<= args; + } + else + { + // need to copy&convert the bitmap, since dx + // canvas uses inline alpha channel + HDC hScreenDC=GetDC(nullptr); + const basegfx::B2ISize aSize = mpBitmap->getSize(); + HBITMAP hBmpBitmap = CreateCompatibleBitmap( hScreenDC, + aSize.getWidth(), + aSize.getHeight() ); + if( !hBmpBitmap ) + return aRes; + + BITMAPINFOHEADER aBIH; + + aBIH.biSize = sizeof( BITMAPINFOHEADER ); + aBIH.biWidth = aSize.getWidth(); + aBIH.biHeight = -aSize.getHeight(); + aBIH.biPlanes = 1; + aBIH.biBitCount = 32; + aBIH.biCompression = BI_RGB; // expects pixel in + // bbggrrxx format + // (little endian) + aBIH.biSizeImage = 0; + aBIH.biXPelsPerMeter = 0; + aBIH.biYPelsPerMeter = 0; + aBIH.biClrUsed = 0; + aBIH.biClrImportant = 0; + + Gdiplus::BitmapData aBmpData; + aBmpData.Width = aSize.getWidth(); + aBmpData.Height = aSize.getHeight(); + aBmpData.Stride = 4*aBmpData.Width; + aBmpData.PixelFormat = PixelFormat32bppARGB; + aBmpData.Scan0 = nullptr; + const Gdiplus::Rect aRect( 0,0,aSize.getWidth(),aSize.getHeight() ); + BitmapSharedPtr pGDIPlusBitmap=mpBitmap->getBitmap(); + if( Gdiplus::Ok != pGDIPlusBitmap->LockBits( &aRect, + Gdiplus::ImageLockModeRead, + PixelFormat32bppARGB, // outputs ARGB (big endian) + &aBmpData ) ) + { + // failed to lock, bail out + return aRes; + } + + // now aBmpData.Scan0 contains our bits - push + // them into HBITMAP, ignoring alpha + SetDIBits( hScreenDC, hBmpBitmap, 0, aSize.getHeight(), aBmpData.Scan0, reinterpret_cast<PBITMAPINFO>(&aBIH), DIB_RGB_COLORS ); + + pGDIPlusBitmap->UnlockBits( &aBmpData ); + + uno::Sequence< uno::Any > args{ uno::Any(reinterpret_cast<sal_Int64>(hBmpBitmap)) }; + aRes <<= args; + } + } + break; + + case 2: + { + if(!mpBitmap->hasAlpha()) + { + return aRes; + } + else + { + static AlphaDIB aDIB; + + // need to copy&convert the bitmap, since dx + // canvas uses inline alpha channel + HDC hScreenDC=GetDC(nullptr); + const basegfx::B2ISize aSize = mpBitmap->getSize(); + HBITMAP hBmpBitmap = CreateCompatibleBitmap( hScreenDC, aSize.getWidth(), aSize.getHeight() ); + if( !hBmpBitmap ) + return aRes; + + aDIB.bmiHeader.biSize = sizeof( BITMAPINFOHEADER ); + aDIB.bmiHeader.biWidth = aSize.getWidth(); + aDIB.bmiHeader.biHeight = -aSize.getHeight(); + aDIB.bmiHeader.biPlanes = 1; + aDIB.bmiHeader.biBitCount = 8; + aDIB.bmiHeader.biCompression = BI_RGB; + aDIB.bmiHeader.biSizeImage = 0; + aDIB.bmiHeader.biXPelsPerMeter = 0; + aDIB.bmiHeader.biYPelsPerMeter = 0; + aDIB.bmiHeader.biClrUsed = 0; + aDIB.bmiHeader.biClrImportant = 0; + + Gdiplus::BitmapData aBmpData; + aBmpData.Width = aSize.getWidth(); + aBmpData.Height = aSize.getHeight(); + aBmpData.Stride = 4*aBmpData.Width; + aBmpData.PixelFormat = PixelFormat32bppARGB; + aBmpData.Scan0 = nullptr; + const Gdiplus::Rect aRect( 0,0,aSize.getWidth(),aSize.getHeight() ); + BitmapSharedPtr pGDIPlusBitmap=mpBitmap->getBitmap(); + if( Gdiplus::Ok != pGDIPlusBitmap->LockBits( &aRect, + Gdiplus::ImageLockModeRead, + PixelFormat32bppARGB, // outputs ARGB (big endian) + &aBmpData ) ) + { + // failed to lock, bail out + return aRes; + } + + // copy only alpha channel to pAlphaBits + const sal_Int32 nScanWidth((aSize.getWidth() + 3) & ~3); + std::unique_ptr<sal_uInt8[]> pAlphaBits( new sal_uInt8[nScanWidth*aSize.getHeight()] ); + const sal_uInt8* pInBits=static_cast<sal_uInt8*>(aBmpData.Scan0); + pInBits+=3; + for( sal_Int32 y=0; y<aSize.getHeight(); ++y ) + { + sal_uInt8* pOutBits=pAlphaBits.get()+y*nScanWidth; + for( sal_Int32 x=0; x<aSize.getWidth(); ++x ) + { + *pOutBits++ = *pInBits; + pInBits += 4; + } + } + + pGDIPlusBitmap->UnlockBits( &aBmpData ); + + // set bits to newly create HBITMAP + SetDIBits( hScreenDC, hBmpBitmap, 0, + aSize.getHeight(), pAlphaBits.get(), + reinterpret_cast<PBITMAPINFO>(&aDIB), DIB_RGB_COLORS ); + + uno::Sequence< uno::Any > args{ uno::Any(reinterpret_cast<sal_Int64>(hBmpBitmap)) }; + aRes <<= args; + } + } + break; + } + + return aRes; + } + + OUString SAL_CALL CanvasBitmap::getImplementationName( ) + { + return "DXCanvas.CanvasBitmap"; + } + + sal_Bool SAL_CALL CanvasBitmap::supportsService( const OUString& ServiceName ) + { + return cppu::supportsService( this, ServiceName ); + } + + uno::Sequence< OUString > SAL_CALL CanvasBitmap::getSupportedServiceNames( ) + { + return { "com.sun.star.rendering.CanvasBitmap" }; + } + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_canvasbitmap.hxx b/canvas/source/directx/dx_canvasbitmap.hxx new file mode 100644 index 0000000000..b1725b447d --- /dev/null +++ b/canvas/source/directx/dx_canvasbitmap.hxx @@ -0,0 +1,91 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/rendering/XBitmapCanvas.hpp> +#include <com/sun/star/rendering/XIntegerBitmap.hpp> +#include <com/sun/star/beans/XFastPropertySet.hpp> +#include <basegfx/vector/b2isize.hxx> +#include <cppuhelper/compbase.hxx> +#include <base/basemutexhelper.hxx> +#include <base/bitmapcanvasbase.hxx> +#include <base/integerbitmapbase.hxx> + +#include "dx_bitmapprovider.hxx" +#include "dx_bitmapcanvashelper.hxx" +#include "dx_devicehelper.hxx" +#include "dx_impltools.hxx" +#include "dx_ibitmap.hxx" + + +/* Definition of CanvasBitmap class */ + +namespace dxcanvas +{ + typedef ::cppu::WeakComponentImplHelper< css::rendering::XBitmapCanvas, + css::rendering::XIntegerBitmap, + css::lang::XServiceInfo, + css::beans::XFastPropertySet > CanvasBitmapBase_Base; + typedef ::canvas::IntegerBitmapBase< + canvas::BitmapCanvasBase2< + ::canvas::BaseMutexHelper< CanvasBitmapBase_Base >, + BitmapCanvasHelper, + ::osl::MutexGuard, + ::cppu::OWeakObject> > CanvasBitmap_Base; + + class CanvasBitmap : public CanvasBitmap_Base, public BitmapProvider + { + public: + /** Create a canvas bitmap for the given surface + + @param rSurface + Surface to create API object for. + + @param rDevice + Reference device, with which bitmap should be compatible + */ + CanvasBitmap( const IBitmapSharedPtr& rSurface, + const DeviceRef& rDevice ); + + /// Dispose all internal references + virtual void disposeThis() override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName( ) override; + virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames( ) override; + + // BitmapProvider + virtual IBitmapSharedPtr getBitmap() const override { return mpBitmap; } + + virtual css::uno::Any SAL_CALL getFastPropertyValue(sal_Int32 nHandle) override; + virtual void SAL_CALL setFastPropertyValue(sal_Int32, const css::uno::Any&) override {} + + private: + /** MUST hold here, too, since CanvasHelper only contains a + raw pointer (without refcounting) + */ + DeviceRef mpDevice; + IBitmapSharedPtr mpBitmap; + }; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_canvascustomsprite.cxx b/canvas/source/directx/dx_canvascustomsprite.cxx new file mode 100644 index 0000000000..b9e79fdc06 --- /dev/null +++ b/canvas/source/directx/dx_canvascustomsprite.cxx @@ -0,0 +1,106 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <memory> + +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/point/b2dpoint.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <rtl/math.hxx> +#include <comphelper/diagnose_ex.hxx> + +#include <canvas/canvastools.hxx> + +#include "dx_canvascustomsprite.hxx" +#include "dx_impltools.hxx" +#include "dx_spritecanvas.hxx" + +using namespace ::com::sun::star; + +namespace dxcanvas +{ + CanvasCustomSprite::CanvasCustomSprite( const css::geometry::RealSize2D& rSpriteSize, + const SpriteCanvasRef& rRefDevice, + const IDXRenderModuleSharedPtr& rRenderModule, + const std::shared_ptr<canvas::ISurfaceProxyManager>& rSurfaceProxy, + bool bShowSpriteBounds ) : + mpSpriteCanvas( rRefDevice ), + mpSurface() + { + ENSURE_OR_THROW( rRefDevice, + "CanvasCustomSprite::CanvasCustomSprite(): Invalid sprite canvas" ); + + mpSurface = std::make_shared<DXSurfaceBitmap>( + ::basegfx::B2ISize( + ::canvas::tools::roundUp( rSpriteSize.Width ), + ::canvas::tools::roundUp( rSpriteSize.Height )), + rSurfaceProxy, + rRenderModule, + true); + + maCanvasHelper.setDevice( *rRefDevice ); + maCanvasHelper.setTarget( mpSurface ); + + maSpriteHelper.init( rSpriteSize, + rRefDevice, + rRenderModule, + mpSurface, + bShowSpriteBounds ); + + // clear sprite to 100% transparent + maCanvasHelper.clear(); + } + + void CanvasCustomSprite::disposeThis() + { + ::osl::MutexGuard aGuard( m_aMutex ); + + mpSurface.reset(); + mpSpriteCanvas.clear(); + + // forward to parent + CanvasCustomSpriteBaseT::disposeThis(); + } + + OUString SAL_CALL CanvasCustomSprite::getImplementationName() + { + return "DXCanvas.CanvasCustomSprite"; + } + + sal_Bool SAL_CALL CanvasCustomSprite::supportsService( const OUString& ServiceName ) + { + return cppu::supportsService( this, ServiceName ); + } + + uno::Sequence< OUString > SAL_CALL CanvasCustomSprite::getSupportedServiceNames() + { + return { "com.sun.star.rendering.CanvasCustomSprite" }; + } + + void CanvasCustomSprite::redraw() const + { + ::osl::MutexGuard aGuard( m_aMutex ); + + maSpriteHelper.redraw( mbSurfaceDirty ); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_canvascustomsprite.hxx b/canvas/source/directx/dx_canvascustomsprite.hxx new file mode 100644 index 0000000000..2411a15273 --- /dev/null +++ b/canvas/source/directx/dx_canvascustomsprite.hxx @@ -0,0 +1,130 @@ +/* -*- 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 . + */ + +#pragma once + +#include <cppuhelper/compbase.hxx> +#include <comphelper/uno3.hxx> + +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/lang/XComponent.hpp> +#include <com/sun/star/rendering/XCustomSprite.hpp> +#include <com/sun/star/rendering/XIntegerBitmap.hpp> +#include <com/sun/star/rendering/XPolyPolygon2D.hpp> + +#include <basegfx/point/b2dpoint.hxx> +#include <basegfx/vector/b2isize.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> + +#include <base/basemutexhelper.hxx> +#include <base/canvascustomspritebase.hxx> + +#include "dx_sprite.hxx" +#include "dx_surfacebitmap.hxx" +#include "dx_bitmapcanvashelper.hxx" +#include "dx_spritehelper.hxx" +#include "dx_spritecanvas.hxx" + + +namespace dxcanvas +{ + typedef ::cppu::WeakComponentImplHelper< css::rendering::XCustomSprite, + css::rendering::XBitmapCanvas, + css::rendering::XIntegerBitmap, + css::lang::XServiceInfo > CanvasCustomSpriteBase_Base; + /** Mixin Sprite + + Have to mixin the Sprite interface before deriving from + ::canvas::CanvasCustomSpriteBase, as this template should + already implement some of those interface methods. + + The reason why this appears kinda convoluted is the fact that + we cannot specify non-IDL types as WeakComponentImplHelper + template args, and furthermore, don't want to derive + ::canvas::CanvasCustomSpriteBase directly from + ::canvas::Sprite (because derivees of + ::canvas::CanvasCustomSpriteBase have to explicitly forward + the XInterface methods (e.g. via DECLARE_UNO3_AGG_DEFAULTS) + anyway). Basically, ::canvas::CanvasCustomSpriteBase should + remain a base class that provides implementation, not to + enforce any specific interface on its derivees. + */ + class CanvasCustomSpriteSpriteBase_Base : public ::canvas::BaseMutexHelper< CanvasCustomSpriteBase_Base >, + public Sprite + { + }; + + typedef ::canvas::CanvasCustomSpriteBase< CanvasCustomSpriteSpriteBase_Base, + SpriteHelper, + BitmapCanvasHelper, + ::osl::MutexGuard, + ::cppu::OWeakObject > CanvasCustomSpriteBaseT; + + /* Definition of CanvasCustomSprite class */ + + class CanvasCustomSprite : public CanvasCustomSpriteBaseT + { + public: + /** Create a custom sprite + + @param rSpriteSize + Size of the sprite in pixel + + @param rRefDevice + Associated output device + + @param rSpriteCanvas + Target canvas + + @param rDevice + Target DX device + */ + CanvasCustomSprite( const css::geometry::RealSize2D& rSpriteSize, + const SpriteCanvasRef& rRefDevice, + const IDXRenderModuleSharedPtr& rRenderModule, + const std::shared_ptr<canvas::ISurfaceProxyManager>& rSurfaceProxy, + bool bShowSpriteBounds ); + + virtual void disposeThis() override; + + // Forwarding the XComponent implementation to the + // cppu::ImplHelper templated base + // Classname Base doing refcount Base implementing the XComponent interface + // | | | + // V V V + DECLARE_UNO3_XCOMPONENT_AGG_DEFAULTS( CanvasCustomSprite, CanvasCustomSpriteBase_Base, ::cppu::WeakComponentImplHelperBase ) + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override; + + // Sprite + virtual void redraw() const override; + + private: + /** MUST hold here, too, since BitmapCanvasHelper only contains a + raw pointer (without refcounting) + */ + SpriteCanvasRef mpSpriteCanvas; + DXSurfaceBitmapSharedPtr mpSurface; + }; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_canvasfont.cxx b/canvas/source/directx/dx_canvasfont.cxx new file mode 100644 index 0000000000..aeb3070525 --- /dev/null +++ b/canvas/source/directx/dx_canvasfont.cxx @@ -0,0 +1,162 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <memory> + +#include <o3tl/char16_t2wchar_t.hxx> + +#include <com/sun/star/rendering/PanoseWeight.hpp> +#include <com/sun/star/rendering/XSpriteCanvas.hpp> +#include <cppuhelper/supportsservice.hxx> + +#include "dx_canvasfont.hxx" +#include "dx_spritecanvas.hxx" +#include "dx_textlayout.hxx" +#include "dx_winstuff.hxx" + +using namespace ::com::sun::star; + +namespace dxcanvas +{ + namespace + { + INT calcFontStyle( const rendering::FontRequest& rFontRequest ) + { + INT nFontStyle( Gdiplus::FontStyleRegular ); + + if( rFontRequest.FontDescription.FontDescription.Weight > rendering::PanoseWeight::BOOK ) + nFontStyle = Gdiplus::FontStyleBold; + + return nFontStyle; + } + } + + CanvasFont::CanvasFont( const rendering::FontRequest& rFontRequest, + const uno::Sequence< beans::PropertyValue >& extraFontProperties, + const geometry::Matrix2D& fontMatrix ) : + CanvasFont_Base( m_aMutex ), + mpGdiPlusUser( GDIPlusUser::createInstance() ), + mpFontFamily(), + mpFont(), + maFontRequest( rFontRequest ), + mnEmphasisMark(0), + maFontMatrix( fontMatrix ) + { + mpFontFamily = std::make_shared<Gdiplus::FontFamily>(o3tl::toW(rFontRequest.FontDescription.FamilyName.getStr()),nullptr); + if( !mpFontFamily->IsAvailable() ) + mpFontFamily = std::make_shared<Gdiplus::FontFamily>(L"Arial",nullptr); + + mpFont = std::make_shared<Gdiplus::Font>( mpFontFamily.get(), + static_cast<Gdiplus::REAL>(rFontRequest.CellSize), + calcFontStyle( rFontRequest ), + Gdiplus::UnitWorld ); + + ::canvas::tools::extractExtraFontProperties(extraFontProperties, mnEmphasisMark); + } + + void SAL_CALL CanvasFont::disposing() + { + ::osl::MutexGuard aGuard( m_aMutex ); + + mpFont.reset(); + mpFontFamily.reset(); + mpGdiPlusUser.reset(); + } + + uno::Reference< rendering::XTextLayout > SAL_CALL CanvasFont::createTextLayout( const rendering::StringContext& aText, + sal_Int8 nDirection, + sal_Int64 nRandomSeed ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + + return new TextLayout( aText, nDirection, nRandomSeed, ImplRef( this ) ); + } + + uno::Sequence< double > SAL_CALL CanvasFont::getAvailableSizes( ) + { + // TODO + return uno::Sequence< double >(); + } + + uno::Sequence< beans::PropertyValue > SAL_CALL CanvasFont::getExtraFontProperties( ) + { + // TODO + return uno::Sequence< beans::PropertyValue >(); + } + + rendering::FontRequest SAL_CALL CanvasFont::getFontRequest( ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + + return maFontRequest; + } + + rendering::FontMetrics SAL_CALL CanvasFont::getFontMetrics( ) + { + // TODO + return rendering::FontMetrics(); + } + + OUString SAL_CALL CanvasFont::getImplementationName() + { + return "DXCanvas::CanvasFont"; + } + + sal_Bool SAL_CALL CanvasFont::supportsService( const OUString& ServiceName ) + { + return cppu::supportsService( this, ServiceName ); + } + + uno::Sequence< OUString > SAL_CALL CanvasFont::getSupportedServiceNames() + { + return { "com.sun.star.rendering.CanvasFont" }; + } + + double CanvasFont::getCellAscent() const + { + ::osl::MutexGuard aGuard( m_aMutex ); + + return mpFontFamily->GetCellAscent(0); // TODO(F1): rFontRequest.styleName + } + + double CanvasFont::getEmHeight() const + { + ::osl::MutexGuard aGuard( m_aMutex ); + + return mpFontFamily->GetEmHeight(0); // TODO(F1): rFontRequest.styleName + } + + FontSharedPtr CanvasFont::getFont() const + { + ::osl::MutexGuard aGuard( m_aMutex ); + + return mpFont; + } + + const css::geometry::Matrix2D& CanvasFont::getFontMatrix() const + { + ::osl::MutexGuard aGuard( m_aMutex ); + + return maFontMatrix; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_canvasfont.hxx b/canvas/source/directx/dx_canvasfont.hxx new file mode 100644 index 0000000000..c5f369d00f --- /dev/null +++ b/canvas/source/directx/dx_canvasfont.hxx @@ -0,0 +1,92 @@ +/* -*- 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 . + */ + +#pragma once + +#include <cppuhelper/compbase.hxx> +#include <cppuhelper/basemutex.hxx> + +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/rendering/XCanvas.hpp> +#include <com/sun/star/rendering/XCanvasFont.hpp> + +#include <rtl/ref.hxx> + +#include <memory> + +#include "dx_winstuff.hxx" +#include "dx_gdiplususer.hxx" + + +/* Definition of CanvasFont class */ + +namespace dxcanvas +{ + typedef std::shared_ptr< Gdiplus::Font > FontSharedPtr; + typedef std::shared_ptr< Gdiplus::FontFamily > FontFamilySharedPtr; + + typedef ::cppu::WeakComponentImplHelper< css::rendering::XCanvasFont, + css::lang::XServiceInfo > CanvasFont_Base; + + class CanvasFont : public ::cppu::BaseMutex, + public CanvasFont_Base + { + public: + typedef rtl::Reference<CanvasFont> ImplRef; + /// make noncopyable + CanvasFont(const CanvasFont&) = delete; + const CanvasFont& operator=(const CanvasFont&) = delete; + + CanvasFont( const css::rendering::FontRequest& fontRequest, + const css::uno::Sequence< css::beans::PropertyValue >& extraFontProperties, + const css::geometry::Matrix2D& fontMatrix ); + + /// Dispose all internal references + virtual void SAL_CALL disposing() override; + + // XCanvasFont + virtual css::uno::Reference< css::rendering::XTextLayout > SAL_CALL createTextLayout( const css::rendering::StringContext& aText, sal_Int8 nDirection, sal_Int64 nRandomSeed ) override; + virtual css::rendering::FontRequest SAL_CALL getFontRequest( ) override; + virtual css::rendering::FontMetrics SAL_CALL getFontMetrics( ) override; + virtual css::uno::Sequence< double > SAL_CALL getAvailableSizes( ) override; + virtual css::uno::Sequence< css::beans::PropertyValue > SAL_CALL getExtraFontProperties( ) override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override; + + double getCellAscent() const; + double getEmHeight() const; + FontSharedPtr getFont() const; + const css::geometry::Matrix2D& getFontMatrix() const; + sal_uInt32 getEmphasisMark() const { return mnEmphasisMark; } + + private: + GDIPlusUserSharedPtr mpGdiPlusUser; + FontFamilySharedPtr mpFontFamily; + FontSharedPtr mpFont; + css::rendering::FontRequest maFontRequest; + sal_uInt32 mnEmphasisMark; + css::geometry::Matrix2D maFontMatrix; + }; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_canvashelper.cxx b/canvas/source/directx/dx_canvashelper.cxx new file mode 100644 index 0000000000..1184886784 --- /dev/null +++ b/canvas/source/directx/dx_canvashelper.cxx @@ -0,0 +1,814 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> +#include <sal/log.hxx> + +#include <algorithm> + +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <basegfx/point/b2dpoint.hxx> +#include <basegfx/utils/canvastools.hxx> +#include <com/sun/star/rendering/CompositeOperation.hpp> +#include <com/sun/star/rendering/PathCapType.hpp> +#include <com/sun/star/rendering/PathJoinType.hpp> +#include <com/sun/star/rendering/RepaintResult.hpp> +#include <com/sun/star/rendering/TexturingMode.hpp> +#include <comphelper/sequence.hxx> +#include <o3tl/char16_t2wchar_t.hxx> +#include <rtl/math.hxx> +#include <comphelper/diagnose_ex.hxx> + +#include <canvas/canvastools.hxx> + +#include "dx_canvasfont.hxx" +#include "dx_canvashelper.hxx" +#include "dx_impltools.hxx" +#include "dx_spritecanvas.hxx" +#include "dx_textlayout.hxx" +#include "dx_vcltools.hxx" + +using namespace ::com::sun::star; + +namespace dxcanvas +{ + namespace + { + Gdiplus::LineCap gdiLineCapFromCap( sal_Int8 nCapType ) + { + switch( nCapType ) + { + case rendering::PathCapType::BUTT: + return Gdiplus::LineCapFlat; + + case rendering::PathCapType::ROUND: + return Gdiplus::LineCapRound; + + case rendering::PathCapType::SQUARE: + return Gdiplus::LineCapSquare; + + default: + ENSURE_OR_THROW( false, + "gdiLineCapFromCap(): Unexpected cap type" ); + } + + return Gdiplus::LineCapFlat; + } + + Gdiplus::DashCap gdiDashCapFromCap( sal_Int8 nCapType ) + { + switch( nCapType ) + { + case rendering::PathCapType::BUTT: + return Gdiplus::DashCapFlat; + + case rendering::PathCapType::ROUND: + return Gdiplus::DashCapRound; + + // Gdiplus does not know square, using flat would make short + // dashes disappear, so use triangle as the closest one. + case rendering::PathCapType::SQUARE: + return Gdiplus::DashCapTriangle; + + default: + ENSURE_OR_THROW( false, + "gdiDashCapFromCap(): Unexpected cap type" ); + } + + return Gdiplus::DashCapFlat; + } + + Gdiplus::LineJoin gdiJoinFromJoin( sal_Int8 nJoinType ) + { + switch( nJoinType ) + { + case rendering::PathJoinType::NONE: + SAL_WARN( "canvas.directx", "gdiJoinFromJoin(): Join NONE not possible, mapping to BEVEL (closest to NONE)" ); + return Gdiplus::LineJoinBevel; + + case rendering::PathJoinType::MITER: + // in GDI+ fallback to Bevel, if miter limit is exceeded, is not done + // by Gdiplus::LineJoinMiter but by Gdiplus::LineJoinMiterClipped + return Gdiplus::LineJoinMiterClipped; + + case rendering::PathJoinType::ROUND: + return Gdiplus::LineJoinRound; + + case rendering::PathJoinType::BEVEL: + return Gdiplus::LineJoinBevel; + + default: + ENSURE_OR_THROW( false, + "gdiJoinFromJoin(): Unexpected join type" ); + } + + return Gdiplus::LineJoinMiter; + } + } + + CanvasHelper::CanvasHelper() : + mpGdiPlusUser( GDIPlusUser::createInstance() ), + mpDevice( nullptr ), + mpGraphicsProvider(), + maOutputOffset() + { + } + + void CanvasHelper::disposing() + { + mpGraphicsProvider.reset(); + mpDevice = nullptr; + mpGdiPlusUser.reset(); + } + + void CanvasHelper::setDevice( rendering::XGraphicDevice& rDevice ) + { + mpDevice = &rDevice; + } + + void CanvasHelper::setTarget( const GraphicsProviderSharedPtr& rTarget ) + { + ENSURE_OR_THROW( rTarget, + "CanvasHelper::setTarget(): Invalid target" ); + ENSURE_OR_THROW( !mpGraphicsProvider, + "CanvasHelper::setTarget(): target set, old target would be overwritten" ); + + mpGraphicsProvider = rTarget; + } + + void CanvasHelper::setTarget( const GraphicsProviderSharedPtr& rTarget, + const ::basegfx::B2ISize& rOutputOffset ) + { + ENSURE_OR_THROW( rTarget, + "CanvasHelper::setTarget(): invalid target" ); + ENSURE_OR_THROW( !mpGraphicsProvider, + "CanvasHelper::setTarget(): target set, old target would be overwritten" ); + + mpGraphicsProvider = rTarget; + maOutputOffset = rOutputOffset; + } + + void CanvasHelper::clear() + { + if( needOutput() ) + { + GraphicsSharedPtr pGraphics( mpGraphicsProvider->getGraphics() ); + Gdiplus::Color aClearColor{Gdiplus::ARGB(Gdiplus::Color::White)}; + + ENSURE_OR_THROW( + Gdiplus::Ok == pGraphics->SetCompositingMode( + Gdiplus::CompositingModeSourceCopy ), // force set, don't blend + "CanvasHelper::clear(): GDI+ SetCompositingMode call failed" ); + ENSURE_OR_THROW( + Gdiplus::Ok == pGraphics->Clear( aClearColor ), + "CanvasHelper::clear(): GDI+ Clear call failed" ); + } + } + + void CanvasHelper::drawPoint( const rendering::XCanvas* /*pCanvas*/, + const geometry::RealPoint2D& aPoint, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState ) + { + if( needOutput() ) + { + GraphicsSharedPtr pGraphics( mpGraphicsProvider->getGraphics() ); + + setupGraphicsState( pGraphics, viewState, renderState ); + + Gdiplus::SolidBrush aBrush( + Gdiplus::Color( + tools::sequenceToArgb(renderState.DeviceColor)) ); + + // determine size of one-by-one device pixel ellipse + Gdiplus::Matrix aMatrix; + pGraphics->GetTransform(&aMatrix); + aMatrix.Invert(); + Gdiplus::PointF vector(1, 1); + aMatrix.TransformVectors(&vector); + + // paint a one-by-one circle, with the given point + // in the middle (rounded to float) + ENSURE_OR_THROW( + Gdiplus::Ok == pGraphics->FillEllipse( &aBrush, + // disambiguate call + Gdiplus::REAL(aPoint.X), + Gdiplus::REAL(aPoint.Y), + Gdiplus::REAL(vector.X), + Gdiplus::REAL(vector.Y) ), + "CanvasHelper::drawPoint(): GDI+ call failed" ); + } + } + + void CanvasHelper::drawLine( const rendering::XCanvas* /*pCanvas*/, + const geometry::RealPoint2D& aStartPoint, + const geometry::RealPoint2D& aEndPoint, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState ) + { + if( needOutput() ) + { + GraphicsSharedPtr pGraphics( mpGraphicsProvider->getGraphics() ); + + setupGraphicsState( pGraphics, viewState, renderState ); + + Gdiplus::Pen aPen( + Gdiplus::Color( + tools::sequenceToArgb(renderState.DeviceColor)), + Gdiplus::REAL(0.0) ); + + // #122683# Switched precedence of pixel offset + // mode. Seemingly, polygon stroking needs + // PixelOffsetModeNone to achieve visually pleasing + // results, whereas all other operations (e.g. polygon + // fills, bitmaps) look better with PixelOffsetModeHalf. + const Gdiplus::PixelOffsetMode aOldMode( + pGraphics->GetPixelOffsetMode() ); + pGraphics->SetPixelOffsetMode( Gdiplus::PixelOffsetModeNone ); + + Gdiplus::Status hr = pGraphics->DrawLine( &aPen, + Gdiplus::REAL(aStartPoint.X), // disambiguate call + Gdiplus::REAL(aStartPoint.Y), + Gdiplus::REAL(aEndPoint.X), + Gdiplus::REAL(aEndPoint.Y) ); + pGraphics->SetPixelOffsetMode( aOldMode ); + + ENSURE_OR_THROW( + Gdiplus::Ok == hr, + "CanvasHelper::drawLine(): GDI+ call failed" ); + } + } + + void CanvasHelper::drawBezier( const rendering::XCanvas* /*pCanvas*/, + const geometry::RealBezierSegment2D& aBezierSegment, + const geometry::RealPoint2D& aEndPoint, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState ) + { + if( needOutput() ) + { + GraphicsSharedPtr pGraphics( mpGraphicsProvider->getGraphics() ); + + setupGraphicsState( pGraphics, viewState, renderState ); + + Gdiplus::Pen aPen( + Gdiplus::Color( + tools::sequenceToArgb(renderState.DeviceColor)), + Gdiplus::REAL(0.0) ); + + // #122683# Switched precedence of pixel offset + // mode. Seemingly, polygon stroking needs + // PixelOffsetModeNone to achieve visually pleasing + // results, whereas all other operations (e.g. polygon + // fills, bitmaps) look better with PixelOffsetModeHalf. + const Gdiplus::PixelOffsetMode aOldMode( + pGraphics->GetPixelOffsetMode() ); + pGraphics->SetPixelOffsetMode( Gdiplus::PixelOffsetModeNone ); + + Gdiplus::Status hr = pGraphics->DrawBezier( &aPen, + Gdiplus::REAL(aBezierSegment.Px), // disambiguate call + Gdiplus::REAL(aBezierSegment.Py), + Gdiplus::REAL(aBezierSegment.C1x), + Gdiplus::REAL(aBezierSegment.C1y), + Gdiplus::REAL(aEndPoint.X), + Gdiplus::REAL(aEndPoint.Y), + Gdiplus::REAL(aBezierSegment.C2x), + Gdiplus::REAL(aBezierSegment.C2y) ); + + pGraphics->SetPixelOffsetMode( aOldMode ); + + ENSURE_OR_THROW( + Gdiplus::Ok == hr, + "CanvasHelper::drawBezier(): GDI+ call failed" ); + } + } + + uno::Reference< rendering::XCachedPrimitive > CanvasHelper::drawPolyPolygon( const rendering::XCanvas* /*pCanvas*/, + const uno::Reference< rendering::XPolyPolygon2D >& xPolyPolygon, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState ) + { + ENSURE_OR_THROW( xPolyPolygon.is(), + "CanvasHelper::drawPolyPolygon: polygon is NULL"); + + if( needOutput() ) + { + GraphicsSharedPtr pGraphics( mpGraphicsProvider->getGraphics() ); + + setupGraphicsState( pGraphics, viewState, renderState ); + + Gdiplus::Pen aPen( + Gdiplus::Color( + tools::sequenceToArgb(renderState.DeviceColor)), + Gdiplus::REAL(0.0) ); + + // #122683# Switched precedence of pixel offset + // mode. Seemingly, polygon stroking needs + // PixelOffsetModeNone to achieve visually pleasing + // results, whereas all other operations (e.g. polygon + // fills, bitmaps) look better with PixelOffsetModeHalf. + const Gdiplus::PixelOffsetMode aOldMode( + pGraphics->GetPixelOffsetMode() ); + pGraphics->SetPixelOffsetMode( Gdiplus::PixelOffsetModeNone ); + + GraphicsPathSharedPtr pPath( tools::graphicsPathFromXPolyPolygon2D( xPolyPolygon ) ); + + // TODO(E1): Return value + Gdiplus::Status hr = pGraphics->DrawPath( &aPen, pPath.get() ); + + pGraphics->SetPixelOffsetMode( aOldMode ); + + ENSURE_OR_THROW( + Gdiplus::Ok == hr, + "CanvasHelper::drawPolyPolygon(): GDI+ call failed" ); + } + + // TODO(P1): Provide caching here. + return uno::Reference< rendering::XCachedPrimitive >(nullptr); + } + + uno::Reference< rendering::XCachedPrimitive > CanvasHelper::strokePolyPolygon( const rendering::XCanvas* /*pCanvas*/, + const uno::Reference< rendering::XPolyPolygon2D >& xPolyPolygon, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState, + const rendering::StrokeAttributes& strokeAttributes ) + { + ENSURE_OR_THROW( xPolyPolygon.is(), + "CanvasHelper::drawPolyPolygon: polygon is NULL"); + + if( needOutput() ) + { + GraphicsSharedPtr pGraphics( mpGraphicsProvider->getGraphics() ); + + setupGraphicsState( pGraphics, viewState, renderState ); + + + // Setup stroke pen + + + Gdiplus::Pen aPen( + Gdiplus::Color( + tools::sequenceToArgb(renderState.DeviceColor)), + static_cast< Gdiplus::REAL >(strokeAttributes.StrokeWidth) ); + + // #122683# Switched precedence of pixel offset + // mode. Seemingly, polygon stroking needs + // PixelOffsetModeNone to achieve visually pleasing + // results, whereas all other operations (e.g. polygon + // fills, bitmaps) look better with PixelOffsetModeHalf. + const Gdiplus::PixelOffsetMode aOldMode( + pGraphics->GetPixelOffsetMode() ); + pGraphics->SetPixelOffsetMode( Gdiplus::PixelOffsetModeNone ); + + const bool bIsMiter(rendering::PathJoinType::MITER == strokeAttributes.JoinType); + const bool bIsNone(rendering::PathJoinType::NONE == strokeAttributes.JoinType); + + if(bIsMiter) + aPen.SetMiterLimit( static_cast< Gdiplus::REAL >(strokeAttributes.MiterLimit) ); + + const std::vector< Gdiplus::REAL >& rDashArray( + ::comphelper::sequenceToContainer< std::vector< Gdiplus::REAL >, double >( + strokeAttributes.DashArray ) ); + if( !rDashArray.empty() ) + { + aPen.SetDashPattern( rDashArray.data(), + rDashArray.size() ); + } + aPen.SetLineCap( gdiLineCapFromCap(strokeAttributes.StartCapType), + gdiLineCapFromCap(strokeAttributes.EndCapType), + gdiDashCapFromCap(strokeAttributes.StartCapType)); + if(!bIsNone) + aPen.SetLineJoin( gdiJoinFromJoin(strokeAttributes.JoinType) ); + + GraphicsPathSharedPtr pPath( tools::graphicsPathFromXPolyPolygon2D( xPolyPolygon, bIsNone ) ); + + // TODO(E1): Return value + Gdiplus::Status hr = pGraphics->DrawPath( &aPen, pPath.get() ); + + pGraphics->SetPixelOffsetMode( aOldMode ); + + ENSURE_OR_THROW( + Gdiplus::Ok == hr, + "CanvasHelper::strokePolyPolygon(): GDI+ call failed" ); + } + + // TODO(P1): Provide caching here. + return uno::Reference< rendering::XCachedPrimitive >(nullptr); + } + + uno::Reference< rendering::XCachedPrimitive > CanvasHelper::strokeTexturedPolyPolygon( const rendering::XCanvas* /*pCanvas*/, + const uno::Reference< rendering::XPolyPolygon2D >& /*xPolyPolygon*/, + const rendering::ViewState& /*viewState*/, + const rendering::RenderState& /*renderState*/, + const uno::Sequence< rendering::Texture >& /*textures*/, + const rendering::StrokeAttributes& /*strokeAttributes*/ ) + { + // TODO + return uno::Reference< rendering::XCachedPrimitive >(nullptr); + } + + uno::Reference< rendering::XCachedPrimitive > CanvasHelper::strokeTextureMappedPolyPolygon( const rendering::XCanvas* /*pCanvas*/, + const uno::Reference< rendering::XPolyPolygon2D >& /*xPolyPolygon*/, + const rendering::ViewState& /*viewState*/, + const rendering::RenderState& /*renderState*/, + const uno::Sequence< rendering::Texture >& /*textures*/, + const uno::Reference< geometry::XMapping2D >& /*xMapping*/, + const rendering::StrokeAttributes& /*strokeAttributes*/ ) + { + // TODO + return uno::Reference< rendering::XCachedPrimitive >(nullptr); + } + + uno::Reference< rendering::XPolyPolygon2D > CanvasHelper::queryStrokeShapes( const rendering::XCanvas* /*pCanvas*/, + const uno::Reference< rendering::XPolyPolygon2D >& /*xPolyPolygon*/, + const rendering::ViewState& /*viewState*/, + const rendering::RenderState& /*renderState*/, + const rendering::StrokeAttributes& /*strokeAttributes*/ ) + { + // TODO + return uno::Reference< rendering::XPolyPolygon2D >(nullptr); + } + + uno::Reference< rendering::XCachedPrimitive > CanvasHelper::fillPolyPolygon( const rendering::XCanvas* /*pCanvas*/, + const uno::Reference< rendering::XPolyPolygon2D >& xPolyPolygon, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState ) + { + ENSURE_OR_THROW( xPolyPolygon.is(), + "CanvasHelper::fillPolyPolygon: polygon is NULL"); + + if( needOutput() ) + { + GraphicsSharedPtr pGraphics( mpGraphicsProvider->getGraphics() ); + + setupGraphicsState( pGraphics, viewState, renderState ); + + Gdiplus::SolidBrush aBrush( + tools::sequenceToArgb(renderState.DeviceColor)); + + GraphicsPathSharedPtr pPath( tools::graphicsPathFromXPolyPolygon2D( xPolyPolygon ) ); + + // TODO(F1): FillRule + ENSURE_OR_THROW( Gdiplus::Ok == pGraphics->FillPath( &aBrush, pPath.get() ), + "CanvasHelper::fillPolyPolygon(): GDI+ call failed " ); + } + + // TODO(P1): Provide caching here. + return uno::Reference< rendering::XCachedPrimitive >(nullptr); + } + + uno::Reference< rendering::XCachedPrimitive > CanvasHelper::fillTextureMappedPolyPolygon( const rendering::XCanvas* /*pCanvas*/, + const uno::Reference< rendering::XPolyPolygon2D >& /*xPolyPolygon*/, + const rendering::ViewState& /*viewState*/, + const rendering::RenderState& /*renderState*/, + const uno::Sequence< rendering::Texture >& /*textures*/, + const uno::Reference< geometry::XMapping2D >& /*xMapping*/ ) + { + // TODO + return uno::Reference< rendering::XCachedPrimitive >(nullptr); + } + + uno::Reference< rendering::XCanvasFont > CanvasHelper::createFont( const rendering::XCanvas* /*pCanvas*/, + const rendering::FontRequest& fontRequest, + const uno::Sequence< beans::PropertyValue >& extraFontProperties, + const geometry::Matrix2D& fontMatrix ) + { + if( needOutput() ) + { + return uno::Reference< rendering::XCanvasFont >( + new CanvasFont(fontRequest, extraFontProperties, fontMatrix ) ); + } + + return uno::Reference< rendering::XCanvasFont >(); + } + + uno::Sequence< rendering::FontInfo > CanvasHelper::queryAvailableFonts( const rendering::XCanvas* /*pCanvas*/, + const rendering::FontInfo& /*aFilter*/, + const uno::Sequence< beans::PropertyValue >& /*aFontProperties*/ ) + { + // TODO + return uno::Sequence< rendering::FontInfo >(); + } + + uno::Reference< rendering::XCachedPrimitive > CanvasHelper::drawText( const rendering::XCanvas* /*pCanvas*/, + const rendering::StringContext& text, + const uno::Reference< rendering::XCanvasFont >& xFont, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState, + sal_Int8 /*textDirection*/ ) + { + ENSURE_OR_THROW( xFont.is(), + "CanvasHelper::drawText: font is NULL"); + + if( needOutput() ) + { + GraphicsSharedPtr pGraphics( mpGraphicsProvider->getGraphics() ); + + setupGraphicsState( pGraphics, viewState, renderState ); + + Gdiplus::SolidBrush aBrush( + Gdiplus::Color( + tools::sequenceToArgb(renderState.DeviceColor))); + + CanvasFont::ImplRef pFont( + tools::canvasFontFromXFont(xFont) ); + + // Move glyphs up, such that output happens at the font + // baseline. + Gdiplus::PointF aPoint( 0.0, + static_cast<Gdiplus::REAL>(-(pFont->getFont()->GetSize()* + pFont->getCellAscent() / + pFont->getEmHeight())) ); + + // TODO(F1): According to + // http://support.microsoft.com/default.aspx?scid=kb;EN-US;Q307208, + // we might have to revert to GDI and ExTextOut here, + // since GDI+ takes the scalability a little bit too + // far... + + // TODO(F2): Proper layout (BiDi, CTL)! IMHO must use + // DrawDriverString here, and perform layouting myself... + ENSURE_OR_THROW( + Gdiplus::Ok == pGraphics->DrawString( o3tl::toW(text.Text.copy( text.StartPosition, + text.Length ).getStr()), + text.Length, + pFont->getFont().get(), + aPoint, + &aBrush ), + "CanvasHelper::drawText(): GDI+ call failed" ); + } + + return uno::Reference< rendering::XCachedPrimitive >(nullptr); + } + + uno::Reference< rendering::XCachedPrimitive > CanvasHelper::drawTextLayout( const rendering::XCanvas* /*pCanvas*/, + const uno::Reference< rendering::XTextLayout >& xLayoutetText, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState ) + { + ENSURE_OR_THROW( xLayoutetText.is(), + "CanvasHelper::drawTextLayout: layout is NULL"); + + if( needOutput() ) + { + TextLayout* pTextLayout = + dynamic_cast< TextLayout* >( xLayoutetText.get() ); + + ENSURE_OR_THROW( pTextLayout, + "CanvasHelper::drawTextLayout(): TextLayout not compatible with this canvas" ); + + pTextLayout->draw( mpGraphicsProvider->getGraphics(), + viewState, + renderState, + maOutputOffset, + mpDevice, + false ); + } + + return uno::Reference< rendering::XCachedPrimitive >(nullptr); + } + + uno::Reference< rendering::XCachedPrimitive > CanvasHelper::drawBitmap( const rendering::XCanvas* /*pCanvas*/, + const uno::Reference< rendering::XBitmap >& xBitmap, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState ) + { + ENSURE_OR_THROW( xBitmap.is(), + "CanvasHelper::drawBitmap: bitmap is NULL"); + + if( needOutput() ) + { + // check whether one of our own objects - need to retrieve + // bitmap _before_ calling + // GraphicsProvider::getGraphics(), to avoid locking our + // own surface. + BitmapSharedPtr pGdiBitmap; + BitmapProvider* pBitmap = dynamic_cast< BitmapProvider* >(xBitmap.get()); + if( pBitmap ) + { + IBitmapSharedPtr pDXBitmap( pBitmap->getBitmap() ); + if( pDXBitmap ) + pGdiBitmap = pDXBitmap->getBitmap(); + } + + GraphicsSharedPtr pGraphics( mpGraphicsProvider->getGraphics() ); + setupGraphicsState( pGraphics, viewState, renderState ); + + if( pGdiBitmap ) + tools::drawGdiPlusBitmap(pGraphics,pGdiBitmap); + else + tools::drawVCLBitmapFromXBitmap(pGraphics, + xBitmap); + } + + // TODO(P1): Provide caching here. + return uno::Reference< rendering::XCachedPrimitive >(nullptr); + } + + uno::Reference< rendering::XCachedPrimitive > CanvasHelper::drawBitmapModulated( const rendering::XCanvas* pCanvas, + const uno::Reference< rendering::XBitmap >& xBitmap, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState ) + { + ENSURE_OR_THROW( xBitmap.is(), + "CanvasHelper::drawBitmap: bitmap is NULL"); + + // no color set -> this is equivalent to a plain drawBitmap(), then + if( renderState.DeviceColor.getLength() < 3 ) + return drawBitmap( pCanvas, xBitmap, viewState, renderState ); + + if( needOutput() ) + { + GraphicsSharedPtr pGraphics( mpGraphicsProvider->getGraphics() ); + + setupGraphicsState( pGraphics, viewState, renderState ); + + BitmapSharedPtr pBitmap( tools::bitmapFromXBitmap( xBitmap ) ); + Gdiplus::Rect aRect( 0, 0, + pBitmap->GetWidth(), + pBitmap->GetHeight() ); + + // Setup an ImageAttributes with an alpha-modulating + // color matrix. + rendering::ARGBColor aARGBColor( + mpDevice->getDeviceColorSpace()->convertToARGB(renderState.DeviceColor)[0]); + + Gdiplus::ImageAttributes aImgAttr; + tools::setModulateImageAttributes( aImgAttr, + aARGBColor.Red, + aARGBColor.Green, + aARGBColor.Blue, + aARGBColor.Alpha ); + + ENSURE_OR_THROW( + Gdiplus::Ok == pGraphics->DrawImage( pBitmap.get(), + aRect, + 0, 0, + pBitmap->GetWidth(), + pBitmap->GetHeight(), + Gdiplus::UnitPixel, + &aImgAttr ), + "CanvasHelper::drawBitmapModulated(): GDI+ call failed" ); + } + + // TODO(P1): Provide caching here. + return uno::Reference< rendering::XCachedPrimitive >(nullptr); + } + + uno::Reference< rendering::XGraphicDevice > CanvasHelper::getDevice() + { + return uno::Reference< rendering::XGraphicDevice >(mpDevice); + } + + // private helper + + + Gdiplus::CompositingMode CanvasHelper::calcCompositingMode( sal_Int8 nMode ) + { + Gdiplus::CompositingMode aRet( Gdiplus::CompositingModeSourceOver ); + + switch( nMode ) + { + case rendering::CompositeOperation::OVER: + case rendering::CompositeOperation::CLEAR: + aRet = Gdiplus::CompositingModeSourceOver; + break; + + case rendering::CompositeOperation::SOURCE: + aRet = Gdiplus::CompositingModeSourceCopy; + break; + + case rendering::CompositeOperation::DESTINATION: + case rendering::CompositeOperation::UNDER: + case rendering::CompositeOperation::INSIDE: + case rendering::CompositeOperation::INSIDE_REVERSE: + case rendering::CompositeOperation::OUTSIDE: + case rendering::CompositeOperation::OUTSIDE_REVERSE: + case rendering::CompositeOperation::ATOP: + case rendering::CompositeOperation::ATOP_REVERSE: + case rendering::CompositeOperation::XOR: + case rendering::CompositeOperation::ADD: + case rendering::CompositeOperation::SATURATE: + // TODO(F2): Problem, because GDI+ only knows about two compositing modes + aRet = Gdiplus::CompositingModeSourceOver; + break; + + default: + ENSURE_OR_THROW( false, "CanvasHelper::calcCompositingMode: unexpected mode" ); + break; + } + + return aRet; + } + + void CanvasHelper::setupGraphicsState( GraphicsSharedPtr const & rGraphics, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState ) + { + ENSURE_OR_THROW( needOutput(), + "CanvasHelper::setupGraphicsState: primary graphics invalid" ); + ENSURE_OR_THROW( mpDevice, + "CanvasHelper::setupGraphicsState: reference device invalid" ); + + // setup view transform first. Clipping e.g. depends on it + ::basegfx::B2DHomMatrix aTransform; + ::canvas::tools::getViewStateTransform(aTransform, viewState); + + // add output offset + if( !maOutputOffset.equalZero() ) + { + const basegfx::B2DHomMatrix aOutputOffset(basegfx::utils::createTranslateB2DHomMatrix( + maOutputOffset.getWidth(), maOutputOffset.getHeight())); + aTransform = aOutputOffset * aTransform; + } + + Gdiplus::Matrix aMatrix; + tools::gdiPlusMatrixFromB2DHomMatrix( aMatrix, aTransform ); + + ENSURE_OR_THROW( + Gdiplus::Ok == rGraphics->SetTransform( &aMatrix ), + "CanvasHelper::setupGraphicsState(): Failed to set GDI+ transformation" ); + + // setup view and render state clipping + ENSURE_OR_THROW( + Gdiplus::Ok == rGraphics->ResetClip(), + "CanvasHelper::setupGraphicsState(): Failed to reset GDI+ clip" ); + + if( viewState.Clip.is() ) + { + GraphicsPathSharedPtr aClipPath( tools::graphicsPathFromXPolyPolygon2D( viewState.Clip ) ); + + // TODO(P3): Cache clip. SetClip( GraphicsPath ) performs abyssmally on GDI+. + // Try SetClip( Rect ) or similar for simple clip paths (need some support in + // LinePolyPolygon, then) + ENSURE_OR_THROW( + Gdiplus::Ok == rGraphics->SetClip( aClipPath.get(), + Gdiplus::CombineModeIntersect ), + "CanvasHelper::setupGraphicsState(): Cannot set GDI+ clip" ); + } + + // setup overall transform only now. View clip above was relative to + // view transform + ::canvas::tools::mergeViewAndRenderTransform(aTransform, + viewState, + renderState); + + // add output offset + if( !maOutputOffset.equalZero() ) + { + const basegfx::B2DHomMatrix aOutputOffset(basegfx::utils::createTranslateB2DHomMatrix( + maOutputOffset.getWidth(), maOutputOffset.getHeight())); + aTransform = aOutputOffset * aTransform; + } + + tools::gdiPlusMatrixFromB2DHomMatrix( aMatrix, aTransform ); + + ENSURE_OR_THROW( + Gdiplus::Ok == rGraphics->SetTransform( &aMatrix ), + "CanvasHelper::setupGraphicsState(): Cannot set GDI+ transformation" ); + + if( renderState.Clip.is() ) + { + GraphicsPathSharedPtr aClipPath( tools::graphicsPathFromXPolyPolygon2D( renderState.Clip ) ); + + // TODO(P3): Cache clip. SetClip( GraphicsPath ) performs abyssmally on GDI+. + // Try SetClip( Rect ) or similar for simple clip paths (need some support in + // LinePolyPolygon, then) + ENSURE_OR_THROW( + Gdiplus::Ok == rGraphics->SetClip( aClipPath.get(), + Gdiplus::CombineModeIntersect ), + "CanvasHelper::setupGraphicsState(): Cannot set GDI+ clip" ); + } + + // setup compositing + const Gdiplus::CompositingMode eCompositing( calcCompositingMode( renderState.CompositeOperation ) ); + ENSURE_OR_THROW( + Gdiplus::Ok == rGraphics->SetCompositingMode( eCompositing ), + "CanvasHelper::setupGraphicsState(): Cannot set GDI* compositing mode)" ); + } + + void CanvasHelper::flush() const + { + if( needOutput() ) + mpGraphicsProvider->getGraphics()->Flush( Gdiplus::FlushIntentionSync ); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_canvashelper.hxx b/canvas/source/directx/dx_canvashelper.hxx new file mode 100644 index 0000000000..54731a08eb --- /dev/null +++ b/canvas/source/directx/dx_canvashelper.hxx @@ -0,0 +1,252 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/rendering/XCanvas.hpp> +#include <com/sun/star/rendering/XBitmapCanvas.hpp> +#include <com/sun/star/rendering/XIntegerBitmap.hpp> + +#include <basegfx/vector/b2isize.hxx> +#include <basegfx/vector/b2dsize.hxx> + +#include "dx_graphicsprovider.hxx" +#include "dx_gdiplususer.hxx" +#include "dx_impltools.hxx" + + +namespace dxcanvas +{ + /** Helper class for basic canvas functionality. Also offers + optional backbuffer painting, when providing it with a second + HDC to render into. + */ + class CanvasHelper + { + public: + CanvasHelper(); + + /// make noncopyable + CanvasHelper(const CanvasHelper&) = delete; + const CanvasHelper& operator=(const CanvasHelper&) = delete; + + /// Release all references + void disposing(); + + /** Initialize canvas helper + + This method late-initializes the canvas helper, providing + it with the necessary device and output objects. Note that + the CanvasHelper does <em>not</em> take ownership of the + passed rDevice reference, nor does it perform any + reference counting. Thus, to prevent the reference counted + SpriteCanvas object from deletion, the user of this class + is responsible for holding ref-counted references itself! + + @param rDevice + Reference device this canvas is associated with + + */ + void setDevice( css::rendering::XGraphicDevice& rDevice ); + + /** Set the target for rendering operations + + @param rTarget + Render target + */ + void setTarget( const GraphicsProviderSharedPtr& rTarget ); + + /** Set the target for rendering operations + + @param rTarget + Render target + + @param rOutputOffset + Output offset in pixel + */ + void setTarget( const GraphicsProviderSharedPtr& rTarget, + const ::basegfx::B2ISize& rOutputOffset ); + + + // CanvasHelper functionality + // ========================== + + // XCanvas (only providing, not implementing the + // interface. Also note subtle method parameter differences) + void clear(); + void drawPoint( const css::rendering::XCanvas* pCanvas, + const css::geometry::RealPoint2D& aPoint, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState ); + void drawLine( const css::rendering::XCanvas* pCanvas, + const css::geometry::RealPoint2D& aStartPoint, + const css::geometry::RealPoint2D& aEndPoint, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState ); + void drawBezier( const css::rendering::XCanvas* pCanvas, + const css::geometry::RealBezierSegment2D& aBezierSegment, + const css::geometry::RealPoint2D& aEndPoint, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState ); + css::uno::Reference< css::rendering::XCachedPrimitive > + drawPolyPolygon( const css::rendering::XCanvas* pCanvas, + const css::uno::Reference< + css::rendering::XPolyPolygon2D >& xPolyPolygon, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState ); + css::uno::Reference< css::rendering::XCachedPrimitive > + strokePolyPolygon( const css::rendering::XCanvas* pCanvas, + const css::uno::Reference< + css::rendering::XPolyPolygon2D >& xPolyPolygon, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState, + const css::rendering::StrokeAttributes& strokeAttributes ); + css::uno::Reference< css::rendering::XCachedPrimitive > + strokeTexturedPolyPolygon( const css::rendering::XCanvas* pCanvas, + const css::uno::Reference< + css::rendering::XPolyPolygon2D >& xPolyPolygon, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState, + const css::uno::Sequence< + css::rendering::Texture >& textures, + const css::rendering::StrokeAttributes& strokeAttributes ); + css::uno::Reference< css::rendering::XCachedPrimitive > + strokeTextureMappedPolyPolygon( const css::rendering::XCanvas* pCanvas, + const css::uno::Reference< + css::rendering::XPolyPolygon2D >& xPolyPolygon, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState, + const css::uno::Sequence< + css::rendering::Texture >& textures, + const css::uno::Reference< + css::geometry::XMapping2D >& xMapping, + const css::rendering::StrokeAttributes& strokeAttributes ); + css::uno::Reference< css::rendering::XPolyPolygon2D > + queryStrokeShapes( const css::rendering::XCanvas* pCanvas, + const css::uno::Reference< + css::rendering::XPolyPolygon2D >& xPolyPolygon, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState, + const css::rendering::StrokeAttributes& strokeAttributes ); + css::uno::Reference< css::rendering::XCachedPrimitive > + fillPolyPolygon( const css::rendering::XCanvas* pCanvas, + const css::uno::Reference< + css::rendering::XPolyPolygon2D >& xPolyPolygon, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState ); + css::uno::Reference< css::rendering::XCachedPrimitive > + fillTexturedPolyPolygon( const css::rendering::XCanvas* pCanvas, + const css::uno::Reference< + css::rendering::XPolyPolygon2D >& xPolyPolygon, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState, + const css::uno::Sequence< + css::rendering::Texture >& textures ); + css::uno::Reference< css::rendering::XCachedPrimitive > + fillTextureMappedPolyPolygon( const css::rendering::XCanvas* pCanvas, + const css::uno::Reference< + css::rendering::XPolyPolygon2D >& xPolyPolygon, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState, + const css::uno::Sequence< + css::rendering::Texture >& textures, + const css::uno::Reference< + css::geometry::XMapping2D >& xMapping ); + + css::uno::Reference< css::rendering::XCanvasFont > + createFont( const css::rendering::XCanvas* pCanvas, + const css::rendering::FontRequest& fontRequest, + const css::uno::Sequence< + css::beans::PropertyValue >& extraFontProperties, + const css::geometry::Matrix2D& fontMatrix ); + + css::uno::Sequence< css::rendering::FontInfo > + queryAvailableFonts( const css::rendering::XCanvas* pCanvas, + const css::rendering::FontInfo& aFilter, + const css::uno::Sequence< + css::beans::PropertyValue >& aFontProperties ); + + css::uno::Reference< css::rendering::XCachedPrimitive > + drawText( const css::rendering::XCanvas* pCanvas, + const css::rendering::StringContext& text, + const css::uno::Reference< + css::rendering::XCanvasFont >& xFont, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState, + sal_Int8 textDirection ); + + css::uno::Reference< css::rendering::XCachedPrimitive > + drawTextLayout( const css::rendering::XCanvas* pCanvas, + const css::uno::Reference< + css::rendering::XTextLayout >& laidOutText, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState ); + + css::uno::Reference< css::rendering::XCachedPrimitive > + drawBitmap( const css::rendering::XCanvas* pCanvas, + const css::uno::Reference< + css::rendering::XBitmap >& xBitmap, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState ); + css::uno::Reference< css::rendering::XCachedPrimitive > + drawBitmapModulated( const css::rendering::XCanvas* pCanvas, + const css::uno::Reference< + css::rendering::XBitmap >& xBitmap, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState ); + css::uno::Reference< css::rendering::XGraphicDevice > + getDevice(); + + // Flush drawing queue to screen + void flush() const; + + /** Called from XCanvas base classes, to notify that content + is _about_ to change + */ + void modifying() {} + + protected: + /// Refcounted global GDI+ state container + GDIPlusUserSharedPtr mpGdiPlusUser; + + /** Phyical output device + + Deliberately not a refcounted reference, because of + potential circular references for spritecanvas. + */ + css::rendering::XGraphicDevice* mpDevice; + + /// Provides the Gdiplus::Graphics to render into + GraphicsProviderSharedPtr mpGraphicsProvider; + + bool needOutput() const { return bool(mpGraphicsProvider); }; + + // returns transparency of color + void setupGraphicsState( GraphicsSharedPtr const & rGraphics, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState ); + + Gdiplus::CompositingMode calcCompositingMode( sal_Int8 nMode ); + + /// Current (transformation-independent) output buffer offset + ::basegfx::B2ISize maOutputOffset; + }; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_canvashelper_texturefill.cxx b/canvas/source/directx/dx_canvashelper_texturefill.cxx new file mode 100644 index 0000000000..c64773539e --- /dev/null +++ b/canvas/source/directx/dx_canvashelper_texturefill.cxx @@ -0,0 +1,608 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <cstdlib> +#include <memory> +#include <tuple> + +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <basegfx/numeric/ftools.hxx> +#include <basegfx/point/b2dpoint.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/range/b2drectangle.hxx> +#include <basegfx/utils/canvastools.hxx> +#include <basegfx/utils/keystoplerp.hxx> +#include <basegfx/utils/lerp.hxx> +#include <basegfx/utils/tools.hxx> +#include <com/sun/star/rendering/TexturingMode.hpp> +#include <rtl/math.hxx> +#include <comphelper/diagnose_ex.hxx> + +#include <parametricpolypolygon.hxx> + +#include "dx_canvashelper.hxx" +#include "dx_impltools.hxx" +#include "dx_spritecanvas.hxx" + + +using namespace ::com::sun::star; + +namespace dxcanvas +{ + namespace + { + typedef std::shared_ptr< Gdiplus::PathGradientBrush > PathGradientBrushSharedPtr; + + bool fillLinearGradient( GraphicsSharedPtr const & rGraphics, + const ::canvas::ParametricPolyPolygon::Values& /*rValues*/, + const std::vector< Gdiplus::Color >& rColors, + const std::vector< Gdiplus::REAL >& rStops, + const GraphicsPathSharedPtr& rFillPath, + const rendering::Texture& texture ) + { + // setup a linear gradient with given colors + + + Gdiplus::LinearGradientBrush aBrush( + Gdiplus::PointF(0.0f, + 0.5f), + Gdiplus::PointF(1.0f, + 0.5f), + rColors[0], + rColors[1] ); + + aBrush.SetInterpolationColors(rColors.data(), + rStops.data(), + rColors.size()); + + // render background color, as LinearGradientBrush does not + // properly support the WrapModeClamp repeat mode + Gdiplus::SolidBrush aBackgroundBrush( rColors[0] ); + rGraphics->FillPath( &aBackgroundBrush, rFillPath.get() ); + + // TODO(F2): This does not yet support other repeat modes + // except clamp, and probably also no multi-texturing + + // calculate parallelogram of gradient in object space, extend + // top and bottom of it such that they cover the whole fill + // path bound area + ::basegfx::B2DHomMatrix aTextureTransform; + ::basegfx::unotools::homMatrixFromAffineMatrix( aTextureTransform, + texture.AffineTransform ); + + ::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 *= aTextureTransform; + aLeftBottom *= aTextureTransform; + aRightTop *= aTextureTransform; + aRightBottom*= aTextureTransform; + + Gdiplus::RectF aBounds; + rFillPath->GetBounds( &aBounds ); + + // now, we potentially have to enlarge our gradient area + // atop and below the transformed [0,1]x[0,1] unit rect, + // for the gradient to fill the complete bound rect. + ::basegfx::utils::infiniteLineFromParallelogram( aLeftTop, + aLeftBottom, + aRightTop, + aRightBottom, + tools::b2dRangeFromGdiPlusRectF( aBounds ) ); + + // calc length of bound rect diagonal + const double nDiagonalLength( + hypot( aBounds.Width, + aBounds.Height ) ); + + // generate a path which covers the 'right' side of the + // gradient, extending two times the bound rect diagonal to + // the right (and thus covering the whole half plane 'right' + // of the gradient). Take the middle of the gradient as the + // 'left' side of the polygon, to not fall victim to rounding + // errors at the edge. + ::basegfx::B2DVector aDirection( aLeftTop - aLeftBottom ); + aDirection = ::basegfx::getNormalizedPerpendicular( aDirection ); + aDirection *= nDiagonalLength; + + const ::basegfx::B2DPoint aHalfPlaneLeftTop( (aLeftTop + aRightTop) * 0.5 ); + const ::basegfx::B2DPoint aHalfPlaneLeftBottom( (aLeftBottom + aRightBottom) * 0.5 ); + const ::basegfx::B2DPoint aHalfPlaneRightTop( aRightTop + aDirection ); + const ::basegfx::B2DPoint aHalfPlaneRightBottom( aRightBottom + aDirection ); + + Gdiplus::GraphicsPath aSolidFillPath; + aSolidFillPath.AddLine( static_cast<Gdiplus::REAL>(aHalfPlaneLeftTop.getX()), + static_cast<Gdiplus::REAL>(aHalfPlaneLeftTop.getY()), + static_cast<Gdiplus::REAL>(aHalfPlaneRightTop.getX()), + static_cast<Gdiplus::REAL>(aHalfPlaneRightTop.getY()) ); + aSolidFillPath.AddLine( static_cast<Gdiplus::REAL>(aHalfPlaneRightBottom.getX()), + static_cast<Gdiplus::REAL>(aHalfPlaneRightBottom.getY()), + static_cast<Gdiplus::REAL>(aHalfPlaneLeftBottom.getX()), + static_cast<Gdiplus::REAL>(aHalfPlaneLeftBottom.getY()) ); + aSolidFillPath.CloseFigure(); + + // limit output to fill path, we've just generated a path that + // might be substantially larger + if( Gdiplus::Ok != rGraphics->SetClip( rFillPath.get(), + Gdiplus::CombineModeIntersect ) ) + { + return false; + } + + Gdiplus::SolidBrush aBackgroundBrush2( rColors.back() ); + rGraphics->FillPath( &aBackgroundBrush2, &aSolidFillPath ); + + // generate clip polygon from the extended parallelogram + // (exploit the feature that distinct lines in a figure are + // automatically closed by a straight line) + Gdiplus::GraphicsPath aClipPath; + aClipPath.AddLine( static_cast<Gdiplus::REAL>(aLeftTop.getX()), + static_cast<Gdiplus::REAL>(aLeftTop.getY()), + static_cast<Gdiplus::REAL>(aRightTop.getX()), + static_cast<Gdiplus::REAL>(aRightTop.getY()) ); + aClipPath.AddLine( static_cast<Gdiplus::REAL>(aRightBottom.getX()), + static_cast<Gdiplus::REAL>(aRightBottom.getY()), + static_cast<Gdiplus::REAL>(aLeftBottom.getX()), + static_cast<Gdiplus::REAL>(aLeftBottom.getY()) ); + aClipPath.CloseFigure(); + + // limit output to a _single_ strip of the gradient (have to + // clip here, since GDI+ wrapmode clamp does not work here) + if( Gdiplus::Ok != rGraphics->SetClip( &aClipPath, + Gdiplus::CombineModeIntersect ) ) + { + return false; + } + + // now, finally, output the gradient + Gdiplus::Matrix aMatrix; + tools::gdiPlusMatrixFromAffineMatrix2D( aMatrix, + texture.AffineTransform ); + aBrush.SetTransform( &aMatrix ); + + rGraphics->FillRectangle( &aBrush, aBounds ); + + return true; + } + + int numColorSteps( const Gdiplus::Color& rColor1, const Gdiplus::Color& rColor2 ) + { + return std::max( + std::abs( rColor1.GetRed() - rColor2.GetRed() ), + std::max( + std::abs( rColor1.GetGreen() - rColor2.GetGreen() ), + std::abs( rColor1.GetBlue() - rColor2.GetBlue() ) ) ); + } + + bool fillPolygonalGradient( const ::canvas::ParametricPolyPolygon::Values& rValues, + const std::vector< Gdiplus::Color >& rColors, + const std::vector< Gdiplus::REAL >& rStops, + GraphicsSharedPtr const & rGraphics, + const GraphicsPathSharedPtr& rPath, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState, + const rendering::Texture& texture ) + { + // copy original fill path object, might have to change it + // below + GraphicsPathSharedPtr pFillPath( rPath ); + const ::basegfx::B2DPolygon& rGradientPoly( rValues.maGradientPoly ); + + PathGradientBrushSharedPtr pGradientBrush; + + // fill background uniformly with end color + Gdiplus::SolidBrush aBackgroundBrush( rColors[0] ); + rGraphics->FillPath( &aBackgroundBrush, pFillPath.get() ); + + Gdiplus::Matrix aMatrix; + // scale focus according to aspect ratio: for wider-than-tall + // bounds (nAspectRatio > 1.0), the focus must have non-zero + // width. Specifically, a bound rect twice as wide as tall has + // a focus of half its width. + if( !::rtl::math::approxEqual(rValues.mnAspectRatio, + 1.0) ) + { + // KLUDGE 1: + + // And here comes the greatest shortcoming of the GDI+ + // gradients ever: SetFocusScales completely ignores + // transformations, both when set at the PathGradientBrush + // and for the world coordinate system. Thus, to correctly + // display anisotrophic path gradients, we have to render + // them by hand. WTF. + + // TODO(F2): This does not yet support other repeat modes + // except clamp, and probably also no multi-texturing + + // limit output to to-be-filled polygon + if( Gdiplus::Ok != rGraphics->SetClip( pFillPath.get(), + Gdiplus::CombineModeIntersect ) ) + { + return false; + } + + // disable anti-aliasing, if any + const Gdiplus::SmoothingMode eOldAAMode( rGraphics->GetSmoothingMode() ); + rGraphics->SetSmoothingMode( Gdiplus::SmoothingModeHighSpeed ); + + + // determine number of steps to use + + + // TODO(Q2): Unify step calculations with VCL canvas + int nColorSteps = 0; + for( size_t i=0; i<rColors.size()-1; ++i ) + nColorSteps += numColorSteps(rColors[i],rColors[i+1]); + ::basegfx::B2DHomMatrix aTotalTransform; + const int nStepCount= + ::canvas::tools::calcGradientStepCount(aTotalTransform, + viewState, + renderState, + texture, + nColorSteps); + + ::basegfx::B2DHomMatrix aTextureTransform; + ::basegfx::unotools::homMatrixFromAffineMatrix( aTextureTransform, + texture.AffineTransform ); + // determine overall transformation for inner polygon (might + // have to be prefixed by anisotrophic scaling) + ::basegfx::B2DHomMatrix aInnerPolygonTransformMatrix; + + // For performance reasons, we create a temporary VCL polygon + // here, keep it all the way and only change the vertex values + // in the loop below (as ::Polygon is a pimpl class, creating + // one every loop turn would really stress the mem allocator) + ::basegfx::B2DPolygon aOuterPoly( rGradientPoly ); + ::basegfx::B2DPolygon aInnerPoly; + + // subdivide polygon _before_ rendering, would otherwise have + // to be performed on every loop turn. + if( aOuterPoly.areControlPointsUsed() ) + aOuterPoly = ::basegfx::utils::adaptiveSubdivideByAngle(aOuterPoly); + + aInnerPoly = aOuterPoly; + aOuterPoly.transform(aTextureTransform); + + + // apply scaling (possibly anisotrophic) to inner polygon + + + // scale inner polygon according to aspect ratio: for + // wider-than-tall bounds (nAspectRatio > 1.0), the inner + // polygon, representing the gradient focus, must have + // non-zero width. Specifically, a bound rect twice as wide as + // tall has a focus polygon of half its width. + const double nAspectRatio( rValues.mnAspectRatio ); + if( nAspectRatio > 1.0 ) + { + // width > height case + aInnerPolygonTransformMatrix.scale( 1.0 - 1.0/nAspectRatio, + 0.0 ); + } + else if( nAspectRatio < 1.0 ) + { + // width < height case + aInnerPolygonTransformMatrix.scale( 0.0, + 1.0 - nAspectRatio ); + } + else + { + // isotrophic case + aInnerPolygonTransformMatrix.scale( 0.0, 0.0 ); + } + + // and finally, add texture transform to it. + aInnerPolygonTransformMatrix *= aTextureTransform; + + // apply final matrix to polygon + aInnerPoly.transform( aInnerPolygonTransformMatrix ); + + Gdiplus::GraphicsPath aCurrPath; + Gdiplus::SolidBrush aFillBrush( rColors[0] ); + const sal_uInt32 nNumPoints( aOuterPoly.count() ); + basegfx::utils::KeyStopLerp aLerper(rValues.maStops); + for( int i=1; i<nStepCount; ++i ) + { + std::ptrdiff_t nIndex; + double fAlpha; + const double fT( i/double(nStepCount) ); + std::tie(nIndex,fAlpha)=aLerper.lerp(fT); + + const Gdiplus::Color aFillColor( + static_cast<BYTE>( basegfx::utils::lerp(rColors[nIndex].GetRed(),rColors[nIndex+1].GetRed(),fAlpha) ), + static_cast<BYTE>( basegfx::utils::lerp(rColors[nIndex].GetGreen(),rColors[nIndex+1].GetGreen(),fAlpha) ), + static_cast<BYTE>( basegfx::utils::lerp(rColors[nIndex].GetBlue(),rColors[nIndex+1].GetBlue(),fAlpha) ) ); + + aFillBrush.SetColor( aFillColor ); + aCurrPath.Reset(); aCurrPath.StartFigure(); + for( unsigned int p=1; p<nNumPoints; ++p ) + { + const ::basegfx::B2DPoint& rOuterPoint1( aOuterPoly.getB2DPoint(p-1) ); + const ::basegfx::B2DPoint& rInnerPoint1( aInnerPoly.getB2DPoint(p-1) ); + const ::basegfx::B2DPoint& rOuterPoint2( aOuterPoly.getB2DPoint(p) ); + const ::basegfx::B2DPoint& rInnerPoint2( aInnerPoly.getB2DPoint(p) ); + + aCurrPath.AddLine( + Gdiplus::REAL(fT*rInnerPoint1.getX() + (1-fT)*rOuterPoint1.getX()), + Gdiplus::REAL(fT*rInnerPoint1.getY() + (1-fT)*rOuterPoint1.getY()), + Gdiplus::REAL(fT*rInnerPoint2.getX() + (1-fT)*rOuterPoint2.getX()), + Gdiplus::REAL(fT*rInnerPoint2.getY() + (1-fT)*rOuterPoint2.getY())); + } + aCurrPath.CloseFigure(); + + rGraphics->FillPath( &aFillBrush, &aCurrPath ); + } + + // reset to old anti-alias mode + rGraphics->SetSmoothingMode( eOldAAMode ); + } + else + { + // KLUDGE 2: + + // We're generating a PathGradientBrush from scratch here, + // and put in a transformed GraphicsPath (transformed with + // the texture transform). This is because the + // straight-forward approach to store a Brush pointer at + // this class and set a texture transform via + // PathGradientBrush::SetTransform() is spoiled by MS: it + // seems that _either_ the texture transform, _or_ the + // transform at the Graphics can be set, but not both. If + // one sets both, only the translational components of the + // texture is respected. + + tools::gdiPlusMatrixFromAffineMatrix2D( aMatrix, + texture.AffineTransform ); + GraphicsPathSharedPtr pGradientPath( + tools::graphicsPathFromB2DPolygon( rValues.maGradientPoly )); + pGradientPath->Transform( &aMatrix ); + + pGradientBrush + = std::make_shared<Gdiplus::PathGradientBrush>( pGradientPath.get() ); + pGradientBrush->SetInterpolationColors( rColors.data(), + rStops.data(), + rStops.size() ); + + // explicitly setup center point. Since the center of GDI+ + // gradients are by default the _centroid_ of the path + // (i.e. the weighted sum of edge points), it will not + // necessarily coincide with our notion of center. + Gdiplus::PointF aCenterPoint(0, 0); + aMatrix.TransformPoints( &aCenterPoint ); + pGradientBrush->SetCenterPoint( aCenterPoint ); + + const bool bTileX( texture.RepeatModeX != rendering::TexturingMode::CLAMP ); + const bool bTileY( texture.RepeatModeY != rendering::TexturingMode::CLAMP ); + + if( bTileX && bTileY ) + pGradientBrush->SetWrapMode( Gdiplus::WrapModeTile ); + else + { + OSL_ENSURE( bTileY == bTileX, + "ParametricPolyPolygon::fillPolygonalGradient(): Cannot have repeat x and repeat y differ!" ); + + pGradientBrush->SetWrapMode( Gdiplus::WrapModeClamp ); + } + + // render actual gradient + rGraphics->FillPath( pGradientBrush.get(), pFillPath.get() ); + } + +#if OSL_DEBUG_LEVEL > 0 + Gdiplus::Pen aPen( Gdiplus::Color( 255, 255, 0, 0 ), + 0.0001f ); + + rGraphics->DrawRectangle( &aPen, + Gdiplus::RectF( 0.0f, 0.0f, + 1.0f, 1.0f ) ); +#endif + + return true; + } + + bool fillGradient( const ::canvas::ParametricPolyPolygon::Values& rValues, + const std::vector< Gdiplus::Color >& rColors, + const std::vector< Gdiplus::REAL >& rStops, + GraphicsSharedPtr const & rGraphics, + const GraphicsPathSharedPtr& rPath, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState, + const rendering::Texture& texture ) + { + switch( rValues.meType ) + { + case ::canvas::ParametricPolyPolygon::GradientType::Linear: + fillLinearGradient( rGraphics, + rValues, + rColors, + rStops, + rPath, + texture ); + break; + + case ::canvas::ParametricPolyPolygon::GradientType::Elliptical: + case ::canvas::ParametricPolyPolygon::GradientType::Rectangular: + fillPolygonalGradient( rValues, + rColors, + rStops, + rGraphics, + rPath, + viewState, + renderState, + texture ); + break; + + default: + ENSURE_OR_THROW( false, + "CanvasHelper::fillGradient(): Unexpected case" ); + } + + return true; + } + + void fillBitmap( const uno::Reference< rendering::XBitmap >& xBitmap, + GraphicsSharedPtr const & rGraphics, + const GraphicsPathSharedPtr& rPath, + const rendering::Texture& rTexture ) + { + OSL_ENSURE( rTexture.RepeatModeX == + rTexture.RepeatModeY, + "CanvasHelper::fillBitmap(): GDI+ cannot handle differing X/Y repeat mode." ); + + const bool bClamp( rTexture.RepeatModeX == rendering::TexturingMode::NONE && + rTexture.RepeatModeY == rendering::TexturingMode::NONE ); + + const geometry::IntegerSize2D aBmpSize( xBitmap->getSize() ); + ENSURE_ARG_OR_THROW( aBmpSize.Width != 0 && + aBmpSize.Height != 0, + "CanvasHelper::fillBitmap(): zero-sized texture bitmap" ); + + // TODO(P3): Detect case that path is rectangle and + // bitmap is just scaled into that. Then, we can + // render directly, without generating a temporary + // GDI+ bitmap (this is significant, because drawing + // layer presents background object bitmap in that + // way!) + BitmapSharedPtr pBitmap( + tools::bitmapFromXBitmap( xBitmap ) ); + + TextureBrushSharedPtr pBrush; + if( ::rtl::math::approxEqual( rTexture.Alpha, + 1.0 ) ) + { + pBrush = std::make_shared<Gdiplus::TextureBrush>( + pBitmap.get(), + bClamp ? Gdiplus::WrapModeClamp : Gdiplus::WrapModeTile ); + } + else + { + Gdiplus::ImageAttributes aImgAttr; + + tools::setModulateImageAttributes( aImgAttr, + 1.0, + 1.0, + 1.0, + rTexture.Alpha ); + + Gdiplus::Rect aRect(0,0, + aBmpSize.Width, + aBmpSize.Height); + pBrush = std::make_shared<Gdiplus::TextureBrush>( + pBitmap.get(), + aRect, + &aImgAttr ); + + pBrush->SetWrapMode( + bClamp ? Gdiplus::WrapModeClamp : Gdiplus::WrapModeTile ); + } + + Gdiplus::Matrix aTextureTransform; + tools::gdiPlusMatrixFromAffineMatrix2D( aTextureTransform, + rTexture.AffineTransform ); + + // scale down bitmap to [0,1]x[0,1] rect, as required + // from the XCanvas interface. + pBrush->MultiplyTransform( &aTextureTransform ); + pBrush->ScaleTransform( static_cast< Gdiplus::REAL >(1.0/aBmpSize.Width), + static_cast< Gdiplus::REAL >(1.0/aBmpSize.Height) ); + + // TODO(F1): FillRule + ENSURE_OR_THROW( + Gdiplus::Ok == rGraphics->FillPath( pBrush.get(), + rPath.get() ), + "CanvasHelper::fillTexturedPolyPolygon(): GDI+ call failed" ); + } + } + + + uno::Reference< rendering::XCachedPrimitive > CanvasHelper::fillTexturedPolyPolygon( const rendering::XCanvas* /*pCanvas*/, + const uno::Reference< rendering::XPolyPolygon2D >& xPolyPolygon, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState, + const uno::Sequence< rendering::Texture >& textures ) + { + ENSURE_OR_THROW( xPolyPolygon.is(), + "CanvasHelper::fillTexturedPolyPolygon: polygon is NULL"); + ENSURE_OR_THROW( textures.getLength(), + "CanvasHelper::fillTexturedPolyPolygon: empty texture sequence"); + + if( needOutput() ) + { + GraphicsSharedPtr pGraphics( mpGraphicsProvider->getGraphics() ); + + setupGraphicsState( pGraphics, viewState, renderState ); + + // TODO(F1): Multi-texturing + if( textures[0].Gradient.is() ) + { + // try to cast XParametricPolyPolygon2D reference to + // our implementation class. + ::canvas::ParametricPolyPolygon* pGradient = + dynamic_cast< ::canvas::ParametricPolyPolygon* >( textures[0].Gradient.get() ); + + if( pGradient ) + { + const ::canvas::ParametricPolyPolygon::Values& rValues( + pGradient->getValues() ); + + OSL_ASSERT(rValues.maColors.getLength() == rValues.maStops.getLength() + && rValues.maColors.getLength() > 1); + + std::vector< Gdiplus::Color > aColors(rValues.maColors.getLength()); + std::transform(&rValues.maColors[0], + &rValues.maColors[0]+rValues.maColors.getLength(), + aColors.begin(), + [](const uno::Sequence< double >& aDoubleSequence) { return tools::sequenceToArgb(aDoubleSequence); } ); + std::vector< Gdiplus::REAL > aStops; + comphelper::sequenceToContainer(aStops,rValues.maStops); + + // TODO(E1): Return value + // TODO(F1): FillRule + fillGradient( rValues, + aColors, + aStops, + pGraphics, + tools::graphicsPathFromXPolyPolygon2D( xPolyPolygon ), + viewState, + renderState, + textures[0] ); + } + } + else if( textures[0].Bitmap.is() ) + { + // TODO(E1): Return value + // TODO(F1): FillRule + fillBitmap( textures[0].Bitmap, + pGraphics, + tools::graphicsPathFromXPolyPolygon2D( xPolyPolygon ), + textures[0] ); + } + } + + // TODO(P1): Provide caching here. + return uno::Reference< rendering::XCachedPrimitive >(nullptr); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_config.cxx b/canvas/source/directx/dx_config.cxx new file mode 100644 index 0000000000..c1d9020845 --- /dev/null +++ b/canvas/source/directx/dx_config.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 <sal/config.h> +#include <sal/log.hxx> + +#include <basegfx/vector/b2ivector.hxx> +#include <com/sun/star/uno/Any.hxx> +#include <com/sun/star/uno/Sequence.hxx> +#include <comphelper/anytostring.hxx> +#include <cppuhelper/exc_hlp.hxx> +#include <osl/diagnose.h> +#include <comphelper/diagnose_ex.hxx> + +#include "dx_config.hxx" + +using namespace com::sun::star; + +namespace dxcanvas +{ + DXCanvasItem::DXCanvasItem() : + ConfigItem( + "Office.Canvas/DXCanvas", + ConfigItemMode::NONE ), + maValues(), + maMaxTextureSize(), + mbDenylistCurrentDevice(false), + mbValuesDirty(false) + { + try + { + uno::Sequence< uno::Any > aProps( GetProperties( { "DeviceDenylist" } )); + uno::Sequence< sal_Int32 > aValues; + + if( aProps.getLength() > 0 && + (aProps[0] >>= aValues) ) + { + const sal_Int32* pValues = aValues.getConstArray(); + const sal_Int32 nNumEntries( aValues.getLength()*sizeof(sal_Int32)/sizeof(DeviceInfo) ); + for( sal_Int32 i=0; i<nNumEntries; ++i ) + { + DeviceInfo aInfo; + aInfo.nVendorId = *pValues++; + aInfo.nDeviceId = *pValues++; + aInfo.nDeviceSubSysId = *pValues++; + aInfo.nDeviceRevision = *pValues++; + aInfo.nDriverId = *pValues++; + aInfo.nDriverVersion = *pValues++; + aInfo.nDriverSubVersion = *pValues++; + aInfo.nDriverBuildId = *pValues++; + maValues.insert(aInfo); + } + } + + aProps = GetProperties( { "DenylistCurrentDevice" } ); + if( aProps.getLength() > 0 ) + aProps[0] >>= mbDenylistCurrentDevice; + + aProps = GetProperties( { "MaxTextureSize" } ); + if( aProps.getLength() > 0 ) + maMaxTextureSize = aProps[0].get<sal_Int32>(); + else + maMaxTextureSize.reset(); + } + catch( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION( "canvas", "" ); + } + } + + DXCanvasItem::~DXCanvasItem() + { + if( !mbValuesDirty ) + return; + + try + { + uno::Sequence< sal_Int32 > aValues( sizeof(DeviceInfo)/sizeof(sal_Int32)*maValues.size() ); + + sal_Int32* pValues = aValues.getArray(); + for( const auto& rValueSet : maValues ) + { + const DeviceInfo& rInfo( rValueSet ); + *pValues++ = rInfo.nVendorId; + *pValues++ = rInfo.nDeviceId; + *pValues++ = rInfo.nDeviceSubSysId; + *pValues++ = rInfo.nDeviceRevision; + *pValues++ = rInfo.nDriverId; + *pValues++ = rInfo.nDriverVersion; + *pValues++ = rInfo.nDriverSubVersion; + *pValues++ = rInfo.nDriverBuildId; + } + + PutProperties({"DeviceDenylist"}, {css::uno::Any(aValues)}); + } + catch( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION( "canvas", "" ); + } + } + + void DXCanvasItem::Notify( const css::uno::Sequence<OUString>& ) {} + void DXCanvasItem::ImplCommit() {} + + bool DXCanvasItem::isDeviceUsable( const DeviceInfo& rDeviceInfo ) const + { + return maValues.find(rDeviceInfo) == maValues.end(); + } + + bool DXCanvasItem::isDenylistCurrentDevice() const + { + return mbDenylistCurrentDevice; + } + + void DXCanvasItem::denylistDevice( const DeviceInfo& rDeviceInfo ) + { + mbValuesDirty = true; + maValues.insert(rDeviceInfo); + } + + void DXCanvasItem::adaptMaxTextureSize( basegfx::B2IVector& io_maxTextureSize ) const + { + if( maMaxTextureSize ) + { + io_maxTextureSize.setX( + std::min( *maMaxTextureSize, + io_maxTextureSize.getX() )); + io_maxTextureSize.setY( + std::min( *maMaxTextureSize, + io_maxTextureSize.getY() )); + } + } + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_config.hxx b/canvas/source/directx/dx_config.hxx new file mode 100644 index 0000000000..14a77d19da --- /dev/null +++ b/canvas/source/directx/dx_config.hxx @@ -0,0 +1,80 @@ +/* -*- 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 . + */ + +#pragma once + +#include <unotools/configitem.hxx> +#include <optional> +#include <set> + +namespace basegfx { class B2IVector; } + +namespace dxcanvas +{ + /** Provide DX canvas config data + */ + class DXCanvasItem : public ::utl::ConfigItem + { + public: + DXCanvasItem(); + + struct DeviceInfo + { + sal_Int32 nVendorId; + sal_Int32 nDeviceId; + sal_Int32 nDeviceSubSysId; + sal_Int32 nDeviceRevision; + + sal_Int32 nDriverId; + sal_Int32 nDriverVersion; + sal_Int32 nDriverSubVersion; + sal_Int32 nDriverBuildId; + + bool operator<( const DeviceInfo& rRHS ) const + { + return nVendorId != rRHS.nVendorId ? nVendorId < rRHS.nVendorId : + (nDeviceId != rRHS.nDeviceId ? nDeviceId < rRHS.nDeviceId : + (nDeviceSubSysId != rRHS.nDeviceSubSysId ? nDeviceSubSysId < rRHS.nDeviceSubSysId : + (nDeviceRevision != rRHS.nDeviceRevision ? nDeviceRevision < rRHS.nDeviceRevision : + (nDriverId != rRHS.nDriverId ? nDriverId < rRHS.nDriverId : + (nDriverVersion != rRHS.nDriverVersion ? nDriverVersion < rRHS.nDriverVersion : + (nDriverSubVersion != rRHS.nDriverSubVersion ? nDriverSubVersion < rRHS.nDriverSubVersion : + (nDriverBuildId != rRHS.nDriverBuildId && nDriverBuildId < rRHS.nDriverBuildId))))))); + } + }; + + ~DXCanvasItem() override; + + bool isDeviceUsable( const DeviceInfo& rDeviceInfo ) const; + bool isDenylistCurrentDevice() const; + void denylistDevice( const DeviceInfo& rDeviceInfo ); + void adaptMaxTextureSize( basegfx::B2IVector& io_maxTextureSize ) const; + virtual void Notify( const css::uno::Sequence<OUString>& aPropertyNames) override; + + private: + virtual void ImplCommit() override; + typedef std::set< DeviceInfo > ValueSet; + ValueSet maValues; + std::optional<sal_Int32> maMaxTextureSize; + bool mbDenylistCurrentDevice; + bool mbValuesDirty; + }; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_devicehelper.cxx b/canvas/source/directx/dx_devicehelper.cxx new file mode 100644 index 0000000000..dada6238e0 --- /dev/null +++ b/canvas/source/directx/dx_devicehelper.cxx @@ -0,0 +1,199 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <memory> + +#include <basegfx/utils/canvastools.hxx> +#include <com/sun/star/lang/NoSupportException.hpp> +#include <comphelper/diagnose_ex.hxx> +#include <vcl/canvastools.hxx> +#include <vcl/outdev.hxx> +#include <vcl/sysdata.hxx> + +#include <canvas/canvastools.hxx> + +#include "dx_canvasbitmap.hxx" +#include "dx_devicehelper.hxx" +#include "dx_linepolypolygon.hxx" +#include "dx_spritecanvas.hxx" +#include "dx_winstuff.hxx" + +using namespace ::com::sun::star; + +namespace dxcanvas +{ + DeviceHelper::DeviceHelper() : + mpDevice( nullptr ), + mnHDC(nullptr), + mpOutDev(nullptr) + { + } + + DeviceHelper::~DeviceHelper() + { + } + + void DeviceHelper::init( HDC hdc, OutputDevice* pOutDev, + rendering::XGraphicDevice& rDevice ) + { + mnHDC = hdc; + mpDevice = &rDevice; + mpOutDev = pOutDev; + } + + void DeviceHelper::disposing() + { + // release all references + mnHDC = nullptr; + mpDevice = nullptr; + mpOutDev = nullptr; + } + + geometry::RealSize2D DeviceHelper::getPhysicalResolution() + { + if( !mpDevice ) + return ::canvas::tools::createInfiniteSize2D(); // we're disposed + + HDC hDC = getHDC(); + ENSURE_OR_THROW( hDC, + "DeviceHelper::getPhysicalResolution(): cannot retrieve HDC from window" ); + + const int nHorzRes( GetDeviceCaps( hDC, + LOGPIXELSX ) ); + const int nVertRes( GetDeviceCaps( hDC, + LOGPIXELSY ) ); + + return geometry::RealSize2D( nHorzRes*25.4, + nVertRes*25.4 ); + } + + geometry::RealSize2D DeviceHelper::getPhysicalSize() + { + if( !mpDevice ) + return ::canvas::tools::createInfiniteSize2D(); // we're disposed + + HDC hDC=getHDC(); + ENSURE_OR_THROW( hDC, + "DeviceHelper::getPhysicalSize(): cannot retrieve HDC from window" ); + + const int nHorzSize( GetDeviceCaps( hDC, + HORZSIZE ) ); + const int nVertSize( GetDeviceCaps( hDC, + VERTSIZE ) ); + + return geometry::RealSize2D( nHorzSize, + nVertSize ); + } + + uno::Reference< rendering::XLinePolyPolygon2D > DeviceHelper::createCompatibleLinePolyPolygon( + const uno::Reference< rendering::XGraphicDevice >& /*rDevice*/, + const uno::Sequence< uno::Sequence< geometry::RealPoint2D > >& points ) + { + if( !mpDevice ) + return uno::Reference< rendering::XLinePolyPolygon2D >(); // we're disposed + + return uno::Reference< rendering::XLinePolyPolygon2D >( + new LinePolyPolygon( + ::basegfx::unotools::polyPolygonFromPoint2DSequenceSequence( points ) ) ); + } + + uno::Reference< rendering::XBezierPolyPolygon2D > DeviceHelper::createCompatibleBezierPolyPolygon( + const uno::Reference< rendering::XGraphicDevice >& /*rDevice*/, + const uno::Sequence< uno::Sequence< geometry::RealBezierSegment2D > >& points ) + { + if( !mpDevice ) + return uno::Reference< rendering::XBezierPolyPolygon2D >(); // we're disposed + + return uno::Reference< rendering::XBezierPolyPolygon2D >( + new LinePolyPolygon( + ::basegfx::unotools::polyPolygonFromBezier2DSequenceSequence( points ) ) ); + } + + uno::Reference< rendering::XBitmap > DeviceHelper::createCompatibleBitmap( + const uno::Reference< rendering::XGraphicDevice >& /*rDevice*/, + const geometry::IntegerSize2D& size ) + { + if( !mpDevice ) + return uno::Reference< rendering::XBitmap >(); // we're disposed + + DXBitmapSharedPtr pBitmap = std::make_shared<DXBitmap>( + ::basegfx::unotools::b2ISizeFromIntegerSize2D(size), + false); + + // create a 24bit RGB system memory surface + return uno::Reference< rendering::XBitmap >(new CanvasBitmap(pBitmap,mpDevice)); + } + + uno::Reference< rendering::XVolatileBitmap > DeviceHelper::createVolatileBitmap( + const uno::Reference< rendering::XGraphicDevice >& /*rDevice*/, + const geometry::IntegerSize2D& /*size*/ ) + { + return uno::Reference< rendering::XVolatileBitmap >(); + } + + uno::Reference< rendering::XBitmap > DeviceHelper::createCompatibleAlphaBitmap( + const uno::Reference< rendering::XGraphicDevice >& /*rDevice*/, + const geometry::IntegerSize2D& size ) + { + if( !mpDevice ) + return uno::Reference< rendering::XBitmap >(); // we're disposed + + DXBitmapSharedPtr pBitmap = std::make_shared<DXBitmap>( + ::basegfx::unotools::b2ISizeFromIntegerSize2D(size), + true); + + // create a 32bit ARGB system memory surface + return uno::Reference< rendering::XBitmap >(new CanvasBitmap(pBitmap,mpDevice)); + } + + uno::Reference< rendering::XVolatileBitmap > DeviceHelper::createVolatileAlphaBitmap( + const uno::Reference< rendering::XGraphicDevice >& /*rDevice*/, + const geometry::IntegerSize2D& /*size*/ ) + { + return uno::Reference< rendering::XVolatileBitmap >(); + } + + uno::Any DeviceHelper::isAccelerated() const + { + return css::uno::Any(false); + } + + uno::Any DeviceHelper::getDeviceHandle() const + { + return uno::Any( reinterpret_cast< sal_Int64 >(mpOutDev.get()) ); + } + + uno::Any DeviceHelper::getSurfaceHandle() const + { + // TODO(F1): expose DirectDraw object + //return mpBackBuffer->getBitmap().get(); + return uno::Any(); + } + + uno::Reference<rendering::XColorSpace> DeviceHelper::getColorSpace() const + { + // always the same + static uno::Reference<rendering::XColorSpace> theSpace = vcl::unotools::createStandardColorSpace(); + return theSpace; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_devicehelper.hxx b/canvas/source/directx/dx_devicehelper.hxx new file mode 100644 index 0000000000..d5fa62b07c --- /dev/null +++ b/canvas/source/directx/dx_devicehelper.hxx @@ -0,0 +1,114 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/awt/Rectangle.hpp> +#include <com/sun/star/rendering/XGraphicDevice.hpp> +#include <com/sun/star/rendering/XBufferController.hpp> + +#include "dx_rendermodule.hxx" +#include "dx_bitmap.hxx" + +#include <rendering/isurfaceproxymanager.hxx> +#include <vcl/vclptr.hxx> + +class OutputDevice; +/* Definition of DeviceHelper class */ + +namespace dxcanvas +{ + class DeviceHelper + { + public: + DeviceHelper(); + ~DeviceHelper(); + + /// make noncopyable + DeviceHelper(const DeviceHelper&) = delete; + const DeviceHelper& operator=(const DeviceHelper&) = delete; + + /** Init the device helper + + @param hdc + private or class dc of the output device. is only stored, + not release + + @param rDevice + Ref back to owning UNO device + */ + void init( HDC hdc, OutputDevice* pOutputDev, + css::rendering::XGraphicDevice& rDevice ); + + /// Dispose all internal references + void disposing(); + + // XWindowGraphicDevice + css::geometry::RealSize2D getPhysicalResolution(); + css::geometry::RealSize2D getPhysicalSize(); + css::uno::Reference< css::rendering::XLinePolyPolygon2D > createCompatibleLinePolyPolygon( + const css::uno::Reference< css::rendering::XGraphicDevice >& rDevice, + const css::uno::Sequence< css::uno::Sequence< css::geometry::RealPoint2D > >& points ); + css::uno::Reference< css::rendering::XBezierPolyPolygon2D > createCompatibleBezierPolyPolygon( + const css::uno::Reference< css::rendering::XGraphicDevice >& rDevice, + const css::uno::Sequence< css::uno::Sequence< css::geometry::RealBezierSegment2D > >& points ); + css::uno::Reference< css::rendering::XBitmap > createCompatibleBitmap( + const css::uno::Reference< css::rendering::XGraphicDevice >& rDevice, + const css::geometry::IntegerSize2D& size ); + css::uno::Reference< css::rendering::XVolatileBitmap > createVolatileBitmap( + const css::uno::Reference< css::rendering::XGraphicDevice >& rDevice, + const css::geometry::IntegerSize2D& size ); + css::uno::Reference< css::rendering::XBitmap > createCompatibleAlphaBitmap( + const css::uno::Reference< css::rendering::XGraphicDevice >& rDevice, + const css::geometry::IntegerSize2D& size ); + css::uno::Reference< css::rendering::XVolatileBitmap > createVolatileAlphaBitmap( + const css::uno::Reference< css::rendering::XGraphicDevice >& rDevice, + const css::geometry::IntegerSize2D& size ); + + css::uno::Any isAccelerated() const; + css::uno::Any getDeviceHandle() const; + css::uno::Any getSurfaceHandle() const; + css::uno::Reference< + css::rendering::XColorSpace > getColorSpace() const; + + /** called when DumpScreenContent property is enabled on + XGraphicDevice, and writes out bitmaps of current screen. + */ + void dumpScreenContent() const {} + + protected: + HDC getHDC() const { return mnHDC; } + css::rendering::XGraphicDevice* getDevice() const { return mpDevice; } + + private: + /** Phyical output device + + Deliberately not a refcounted reference, because of + potential circular references for canvas. Needed to + create bitmaps + */ + css::rendering::XGraphicDevice* mpDevice; + HDC mnHDC; + VclPtr<OutputDevice> mpOutDev; + }; + + typedef ::rtl::Reference< css::rendering::XGraphicDevice > DeviceRef; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_gdiplususer.cxx b/canvas/source/directx/dx_gdiplususer.cxx new file mode 100644 index 0000000000..85c143cff4 --- /dev/null +++ b/canvas/source/directx/dx_gdiplususer.cxx @@ -0,0 +1,74 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <osl/mutex.hxx> + +#include "dx_gdiplususer.hxx" +#include "dx_winstuff.hxx" + + +namespace dxcanvas +{ + namespace + { + ::osl::Mutex* p_gdiPlusUsageCountMutex( osl::Mutex::getGlobalMutex() ); + int n_gdiPlusUsageCount( 0 ); + + ULONG_PTR a_GdiPlusToken; // GDI+ handle. Owned by this object + } + + GDIPlusUser::GDIPlusUserSharedPtr GDIPlusUser::createInstance() + { + return GDIPlusUserSharedPtr( new GDIPlusUser() ); + } + + GDIPlusUser::~GDIPlusUser() + { + ::osl::MutexGuard aGuard( *p_gdiPlusUsageCountMutex ); + + --n_gdiPlusUsageCount; + + if( n_gdiPlusUsageCount == 0 ) + Gdiplus::GdiplusShutdown( a_GdiPlusToken ); + } + + GDIPlusUser::GDIPlusUser() + { + ::osl::MutexGuard aGuard( *p_gdiPlusUsageCountMutex ); + + if( n_gdiPlusUsageCount == 0 ) + { + // Setup GDI+ + + // No extras here, simply taking GdiplusStartupInput's + // default constructor + Gdiplus::GdiplusStartupInput gdiPlusStartupInput; + + Gdiplus::GdiplusStartup( &a_GdiPlusToken, + &gdiPlusStartupInput, + nullptr ); + } + + ++n_gdiPlusUsageCount; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_gdiplususer.hxx b/canvas/source/directx/dx_gdiplususer.hxx new file mode 100644 index 0000000000..4b0a0f52aa --- /dev/null +++ b/canvas/source/directx/dx_gdiplususer.hxx @@ -0,0 +1,45 @@ +/* -*- 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 . + */ + +#pragma once + +#include <sal/config.h> +#include <memory> + +/* Definition of GDIPlusUser class */ + +namespace dxcanvas +{ + class GDIPlusUser + { + public: + typedef std::shared_ptr< GDIPlusUser > GDIPlusUserSharedPtr; + + static GDIPlusUserSharedPtr createInstance(); + ~GDIPlusUser(); + + private: + GDIPlusUser(); // create us via factory method + }; + + typedef GDIPlusUser::GDIPlusUserSharedPtr GDIPlusUserSharedPtr; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_graphicsprovider.hxx b/canvas/source/directx/dx_graphicsprovider.hxx new file mode 100644 index 0000000000..2d6d618842 --- /dev/null +++ b/canvas/source/directx/dx_graphicsprovider.hxx @@ -0,0 +1,46 @@ +/* -*- 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 . + */ + +#pragma once + +#include "dx_winstuff.hxx" +#include <memory> + +namespace Gdiplus{ class Graphics; } + +namespace dxcanvas +{ + /** Provider of a Gdiplus::Graphics. Interface + */ + class GraphicsProvider + { + public: + GraphicsProvider() = default; + virtual ~GraphicsProvider() {} + /// make noncopyable + GraphicsProvider(const GraphicsProvider&) = delete; + GraphicsProvider& operator=(const GraphicsProvider&) = delete; + + virtual GraphicsSharedPtr getGraphics() = 0; + }; + + typedef std::shared_ptr< GraphicsProvider > GraphicsProviderSharedPtr; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_ibitmap.hxx b/canvas/source/directx/dx_ibitmap.hxx new file mode 100644 index 0000000000..9669f67674 --- /dev/null +++ b/canvas/source/directx/dx_ibitmap.hxx @@ -0,0 +1,62 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/rendering/XCanvas.hpp> +#include <com/sun/star/rendering/XIntegerBitmap.hpp> +#include <basegfx/vector/b2isize.hxx> +#include <basegfx/vector/b2ivector.hxx> +#include <basegfx/point/b2dpoint.hxx> +#include <basegfx/range/b2drange.hxx> +#include <memory> +#include "dx_graphicsprovider.hxx" + +namespace dxcanvas +{ + /// Interface for internal canvas bitmap objects + struct IBitmap : public GraphicsProvider + { + virtual BitmapSharedPtr getBitmap() const = 0; + virtual ::basegfx::B2ISize getSize() const = 0; + virtual bool hasAlpha() const = 0; + + virtual css::uno::Sequence< sal_Int8 > getData( + css::rendering::IntegerBitmapLayout& bitmapLayout, + const css::geometry::IntegerRectangle2D& rect ) = 0; + + virtual void setData( + const css::uno::Sequence< sal_Int8 >& data, + const css::rendering::IntegerBitmapLayout& bitmapLayout, + const css::geometry::IntegerRectangle2D& rect ) = 0; + + virtual void setPixel( + const css::uno::Sequence< sal_Int8 >& color, + const css::rendering::IntegerBitmapLayout& bitmapLayout, + const css::geometry::IntegerPoint2D& pos ) = 0; + + virtual css::uno::Sequence< sal_Int8 > getPixel( + css::rendering::IntegerBitmapLayout& bitmapLayout, + const css::geometry::IntegerPoint2D& pos ) = 0; + }; + + typedef std::shared_ptr<IBitmap> IBitmapSharedPtr; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_impltools.cxx b/canvas/source/directx/dx_impltools.cxx new file mode 100644 index 0000000000..0364ebcbdd --- /dev/null +++ b/canvas/source/directx/dx_impltools.cxx @@ -0,0 +1,629 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <algorithm> +#include <memory> +#include <vector> + + +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/numeric/ftools.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/polygon/b2dpolypolygon.hxx> +#include <basegfx/range/b2drectangle.hxx> +#include <basegfx/range/b2irectangle.hxx> +#include <basegfx/utils/canvastools.hxx> +#include <com/sun/star/geometry/IntegerRectangle2D.hpp> +#include <com/sun/star/geometry/RealPoint2D.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/lang/XUnoTunnel.hpp> +#include <comphelper/diagnose_ex.hxx> + +#include <canvas/canvastools.hxx> +#include <verifyinput.hxx> + +#include "dx_canvas.hxx" +#include "dx_canvasbitmap.hxx" +#include "dx_canvasfont.hxx" +#include "dx_impltools.hxx" +#include "dx_linepolypolygon.hxx" +#include "dx_spritecanvas.hxx" +#include "dx_vcltools.hxx" + + +using namespace ::com::sun::star; + + +namespace dxcanvas::tools +{ + ::basegfx::B2DPolyPolygon polyPolygonFromXPolyPolygon2D( const uno::Reference< rendering::XPolyPolygon2D >& xPoly ) + { + LinePolyPolygon* pPolyImpl = dynamic_cast< LinePolyPolygon* >( xPoly.get() ); + + if( pPolyImpl ) + { + return pPolyImpl->getPolyPolygon(); + } + else + { + const sal_Int32 nPolys( xPoly->getNumberOfPolygons() ); + + // not a known implementation object - try data source + // interfaces + uno::Reference< rendering::XBezierPolyPolygon2D > xBezierPoly( + xPoly, + uno::UNO_QUERY ); + + if( xBezierPoly.is() ) + { + return ::basegfx::unotools::polyPolygonFromBezier2DSequenceSequence( + xBezierPoly->getBezierSegments( 0, + nPolys, + 0, + -1 ) ); + } + else + { + uno::Reference< rendering::XLinePolyPolygon2D > xLinePoly( + xPoly, + uno::UNO_QUERY ); + + // no implementation class and no data provider + // found - contract violation. + ENSURE_ARG_OR_THROW( xLinePoly.is(), + "VCLCanvas::polyPolygonFromXPolyPolygon2D(): Invalid input " + "poly-polygon, cannot retrieve vertex data" ); + + return ::basegfx::unotools::polyPolygonFromPoint2DSequenceSequence( + xLinePoly->getPoints( 0, + nPolys, + 0, + -1 ) ); + } + } + } + + void setupGraphics( Gdiplus::Graphics& rGraphics ) + { + // setup graphics with (somewhat arbitrary) defaults + //rGraphics.SetCompositingQuality( Gdiplus::CompositingQualityHighQuality ); + rGraphics.SetCompositingQuality( Gdiplus::CompositingQualityHighSpeed ); + //rGraphics.SetInterpolationMode( Gdiplus::InterpolationModeHighQualityBilinear ); // with prefiltering for shrinks + rGraphics.SetInterpolationMode( Gdiplus::InterpolationModeBilinear ); + + // #122683# Switched precedence of pixel offset + // mode. Seemingly, polygon stroking needs + // PixelOffsetModeNone to achieve visually pleasing + // results, whereas all other operations (e.g. polygon + // fills, bitmaps) look better with PixelOffsetModeHalf. + rGraphics.SetPixelOffsetMode( Gdiplus::PixelOffsetModeHalf ); // Pixel center at (0.5, 0.5) etc. + //rGraphics.SetPixelOffsetMode( Gdiplus::PixelOffsetModeNone ); + + //rGraphics.SetSmoothingMode( Gdiplus::SmoothingModeHighSpeed ); // no line/curve antialiasing + //rGraphics.SetSmoothingMode( Gdiplus::SmoothingModeHighQuality ); + rGraphics.SetSmoothingMode( Gdiplus::SmoothingModeAntiAlias ); + //rGraphics.SetTextRenderingHint( Gdiplus::TextRenderingHintAntiAlias ); + rGraphics.SetTextRenderingHint( Gdiplus::TextRenderingHintSystemDefault ); + rGraphics.SetPageUnit(Gdiplus::UnitPixel); + } + + Gdiplus::Graphics* createGraphicsFromHDC(HDC aHDC) + { + Gdiplus::Graphics* pRet = new Gdiplus::Graphics(aHDC); + setupGraphics( *pRet ); + return pRet; + } + + GraphicsSharedPtr createGraphicsFromBitmap(const BitmapSharedPtr& rBitmap) + { + GraphicsSharedPtr pRet(Gdiplus::Graphics::FromImage(rBitmap.get())); + if( pRet ) + setupGraphics( *pRet ); + return pRet; + } + + void gdiPlusMatrixFromB2DHomMatrix( Gdiplus::Matrix& rGdiplusMatrix, const ::basegfx::B2DHomMatrix& rMatrix ) + { + rGdiplusMatrix.SetElements( static_cast<Gdiplus::REAL>(rMatrix.get(0,0)), + static_cast<Gdiplus::REAL>(rMatrix.get(1,0)), + static_cast<Gdiplus::REAL>(rMatrix.get(0,1)), + static_cast<Gdiplus::REAL>(rMatrix.get(1,1)), + static_cast<Gdiplus::REAL>(rMatrix.get(0,2)), + static_cast<Gdiplus::REAL>(rMatrix.get(1,2)) ); + } + + void gdiPlusMatrixFromAffineMatrix2D( Gdiplus::Matrix& rGdiplusMatrix, + const geometry::AffineMatrix2D& rMatrix ) + { + rGdiplusMatrix.SetElements( static_cast<Gdiplus::REAL>(rMatrix.m00), + static_cast<Gdiplus::REAL>(rMatrix.m10), + static_cast<Gdiplus::REAL>(rMatrix.m01), + static_cast<Gdiplus::REAL>(rMatrix.m11), + static_cast<Gdiplus::REAL>(rMatrix.m02), + static_cast<Gdiplus::REAL>(rMatrix.m12) ); + } + + namespace + { + // TODO(P2): Check whether this gets inlined. If not, make functor + // out of it + Gdiplus::PointF implGdiPlusPointFromRealPoint2D( const css::geometry::RealPoint2D& rPoint ) + { + return Gdiplus::PointF( static_cast<Gdiplus::REAL>(rPoint.X), + static_cast<Gdiplus::REAL>(rPoint.Y) ); + } + + void graphicsPathFromB2DPolygon( GraphicsPathSharedPtr const & rOutput, + std::vector< Gdiplus::PointF >& rPoints, + const ::basegfx::B2DPolygon& rPoly, + bool bNoLineJoin) + { + const sal_uInt32 nPoints( rPoly.count() ); + + if( nPoints < 2 ) + return; + + rOutput->StartFigure(); + + const bool bClosedPolygon( rPoly.isClosed() ); + + if( rPoly.areControlPointsUsed() ) + { + // control points used -> for now, add all + // segments as curves to GraphicsPath + + // If the polygon is closed, we need to add the + // first point, thus, one more (can't simply + // GraphicsPath::CloseFigure() it, since the last + // point cannot have any control points for GDI+) + rPoints.resize( 3*nPoints + (bClosedPolygon ? 1 : 0) ); + + sal_uInt32 nCurrOutput=0; + for( sal_uInt32 nCurrPoint=0; nCurrPoint<nPoints; ++nCurrPoint ) + { + const ::basegfx::B2DPoint& rPoint( rPoly.getB2DPoint( nCurrPoint ) ); + rPoints[nCurrOutput++] = Gdiplus::PointF( static_cast<Gdiplus::REAL>(rPoint.getX()), + static_cast<Gdiplus::REAL>(rPoint.getY()) ); + + const ::basegfx::B2DPoint& rControlPointA( rPoly.getNextControlPoint( nCurrPoint ) ); + rPoints[nCurrOutput++] = Gdiplus::PointF( static_cast<Gdiplus::REAL>(rControlPointA.getX()), + static_cast<Gdiplus::REAL>(rControlPointA.getY()) ); + + const ::basegfx::B2DPoint& rControlPointB( rPoly.getPrevControlPoint( (nCurrPoint + 1) % nPoints) ); + rPoints[nCurrOutput++] = Gdiplus::PointF( static_cast<Gdiplus::REAL>(rControlPointB.getX()), + static_cast<Gdiplus::REAL>(rControlPointB.getY()) ); + } + + if( bClosedPolygon ) + { + // add first point again (to be able to pass + // control points for the last point, see + // above) + const ::basegfx::B2DPoint& rPoint( rPoly.getB2DPoint(0) ); + rPoints[nCurrOutput++] = Gdiplus::PointF( static_cast<Gdiplus::REAL>(rPoint.getX()), + static_cast<Gdiplus::REAL>(rPoint.getY()) ); + + if(bNoLineJoin && nCurrOutput > 7) + { + for(sal_uInt32 a(3); a < nCurrOutput; a+=3) + { + rOutput->StartFigure(); + rOutput->AddBezier(rPoints[a - 3], rPoints[a - 2], rPoints[a - 1], rPoints[a]); + } + } + else + { + rOutput->AddBeziers( rPoints.data(), nCurrOutput ); + } + } + else + { + // GraphicsPath expects 3(n-1)+1 points (i.e. the + // last point must not have any trailing control + // points after it). + // Therefore, simply don't pass the last two + // points here. + if( nCurrOutput > 3 ) + { + if(bNoLineJoin && nCurrOutput > 7) + { + for(sal_uInt32 a(3); a < nCurrOutput; a+=3) + { + rOutput->StartFigure(); + rOutput->AddBezier(rPoints[a - 3], rPoints[a - 2], rPoints[a - 1], rPoints[a]); + } + } + else + { + rOutput->AddBeziers( rPoints.data(), nCurrOutput-2 ); + } + } + } + } + else + { + // no control points -> no curves, simply add + // straight lines to GraphicsPath + rPoints.resize( nPoints ); + + for( sal_uInt32 nCurrPoint=0; nCurrPoint<nPoints; ++nCurrPoint ) + { + const ::basegfx::B2DPoint& rPoint( rPoly.getB2DPoint( nCurrPoint ) ); + rPoints[nCurrPoint] = Gdiplus::PointF( static_cast<Gdiplus::REAL>(rPoint.getX()), + static_cast<Gdiplus::REAL>(rPoint.getY()) ); + } + + if(bNoLineJoin && nPoints > 2) + { + for(sal_uInt32 a(1); a < nPoints; a++) + { + rOutput->StartFigure(); + rOutput->AddLine(rPoints[a - 1], rPoints[a]); + } + + if(bClosedPolygon) + { + rOutput->StartFigure(); + rOutput->AddLine(rPoints[nPoints - 1], rPoints[0]); + } + } + else + { + rOutput->AddLines( rPoints.data(), nPoints ); + } + } + + if( bClosedPolygon && !bNoLineJoin ) + rOutput->CloseFigure(); + } + } + + Gdiplus::Rect gdiPlusRectFromIntegerRectangle2D( const geometry::IntegerRectangle2D& rRect ) + { + return Gdiplus::Rect( rRect.X1, + rRect.Y1, + rRect.X2 - rRect.X1, + rRect.Y2 - rRect.Y1 ); + } + + Gdiplus::RectF gdiPlusRectFFromRectangle2D( const geometry::RealRectangle2D& rRect ) + { + return Gdiplus::RectF( static_cast<Gdiplus::REAL>(rRect.X1), + static_cast<Gdiplus::REAL>(rRect.Y1), + static_cast<Gdiplus::REAL>(rRect.X2 - rRect.X1), + static_cast<Gdiplus::REAL>(rRect.Y2 - rRect.Y1) ); + } + + RECT gdiRectFromB2IRect( const ::basegfx::B2IRange& rRect ) + { + RECT aRect = {rRect.getMinX(), + rRect.getMinY(), + rRect.getMaxX(), + rRect.getMaxY()}; + + return aRect; + } + + geometry::RealPoint2D realPoint2DFromGdiPlusPointF( const Gdiplus::PointF& rPoint ) + { + return geometry::RealPoint2D( rPoint.X, rPoint.Y ); + } + + geometry::RealRectangle2D realRectangle2DFromGdiPlusRectF( const Gdiplus::RectF& rRect ) + { + return geometry::RealRectangle2D( rRect.X, rRect.Y, + rRect.X + rRect.Width, + rRect.Y + rRect.Height ); + } + + ::basegfx::B2DPoint b2dPointFromGdiPlusPointF( const Gdiplus::PointF& rPoint ) + { + return ::basegfx::B2DPoint( rPoint.X, rPoint.Y ); + } + + ::basegfx::B2DRange b2dRangeFromGdiPlusRectF( const Gdiplus::RectF& rRect ) + { + return ::basegfx::B2DRange( rRect.X, rRect.Y, + rRect.X + rRect.Width, + rRect.Y + rRect.Height ); + } + + uno::Sequence< sal_Int8 > argbToIntSequence( Gdiplus::ARGB rColor ) + { + // TODO(F1): handle color space conversions, when defined on canvas/graphicDevice + return + { + static_cast<sal_Int8>((rColor >> 16) & 0xFF), // red + static_cast<sal_Int8>((rColor >> 8) & 0xFF), // green + static_cast<sal_Int8>(rColor & 0xFF), // blue + static_cast<sal_Int8>((rColor >> 24) & 0xFF) // alpha + }; + } + + Gdiplus::ARGB sequenceToArgb( const uno::Sequence< sal_Int8 >& rColor ) + { + ENSURE_OR_THROW( rColor.getLength() > 2, + "sequenceToArgb: need at least three channels" ); + + // TODO(F1): handle color space conversions, when defined on canvas/graphicDevice + Gdiplus::ARGB aColor; + + aColor = (static_cast<sal_uInt8>(rColor[0]) << 16) | (static_cast<sal_uInt8>(rColor[1]) << 8) | static_cast<sal_uInt8>(rColor[2]); + + if( rColor.getLength() > 3 ) + aColor |= static_cast<sal_uInt8>(rColor[3]) << 24; + + return aColor; + } + + Gdiplus::ARGB sequenceToArgb( const uno::Sequence< double >& rColor ) + { + ENSURE_OR_THROW( rColor.getLength() > 2, + "sequenceToColor: need at least three channels" ); + + // TODO(F1): handle color space conversions, when defined on canvas/graphicDevice + Gdiplus::ARGB aColor; + + ::canvas::tools::verifyRange(rColor[0],0.0,1.0); + ::canvas::tools::verifyRange(rColor[1],0.0,1.0); + ::canvas::tools::verifyRange(rColor[2],0.0,1.0); + + aColor = + (static_cast<sal_uInt8>( ::basegfx::fround( 255*rColor[0] ) ) << 16) | + (static_cast<sal_uInt8>( ::basegfx::fround( 255*rColor[1] ) ) << 8) | + static_cast<sal_uInt8>( ::basegfx::fround( 255*rColor[2] ) ); + + if( rColor.getLength() > 3 ) + { + ::canvas::tools::verifyRange(rColor[3],0.0,1.0); + aColor |= static_cast<sal_uInt8>( ::basegfx::fround( 255*rColor[3] ) ) << 24; + } + + return aColor; + } + + GraphicsPathSharedPtr graphicsPathFromRealPoint2DSequence( const uno::Sequence< uno::Sequence< geometry::RealPoint2D > >& points ) + { + GraphicsPathSharedPtr pRes = std::make_shared<Gdiplus::GraphicsPath>(); + std::vector< Gdiplus::PointF > aPoints; + + for( uno::Sequence< geometry::RealPoint2D > const & seqPoints : points ) + { + const sal_Int32 nCurrSize( seqPoints.getLength() ); + if( nCurrSize ) + { + aPoints.resize( nCurrSize ); + + // TODO(F1): Closed/open polygons + + // convert from RealPoint2D array to Gdiplus::PointF array + std::transform( seqPoints.getConstArray(), + seqPoints.getConstArray()+nCurrSize, + aPoints.begin(), + implGdiPlusPointFromRealPoint2D ); + + pRes->AddLines( aPoints.data(), nCurrSize ); + } + } + + return pRes; + } + + GraphicsPathSharedPtr graphicsPathFromB2DPolygon( const ::basegfx::B2DPolygon& rPoly, bool bNoLineJoin ) + { + GraphicsPathSharedPtr pRes = std::make_shared<Gdiplus::GraphicsPath>(); + std::vector< Gdiplus::PointF > aPoints; + + graphicsPathFromB2DPolygon( pRes, aPoints, rPoly, bNoLineJoin ); + + return pRes; + } + + GraphicsPathSharedPtr graphicsPathFromB2DPolyPolygon( const ::basegfx::B2DPolyPolygon& rPoly, bool bNoLineJoin ) + { + GraphicsPathSharedPtr pRes = std::make_shared<Gdiplus::GraphicsPath>(); + std::vector< Gdiplus::PointF > aPoints; + + const sal_uInt32 nPolies( rPoly.count() ); + for( sal_uInt32 nCurrPoly=0; nCurrPoly<nPolies; ++nCurrPoly ) + { + graphicsPathFromB2DPolygon( pRes, + aPoints, + rPoly.getB2DPolygon( nCurrPoly ), + bNoLineJoin); + } + + return pRes; + } + + GraphicsPathSharedPtr graphicsPathFromXPolyPolygon2D( const uno::Reference< rendering::XPolyPolygon2D >& xPoly, bool bNoLineJoin ) + { + LinePolyPolygon* pPolyImpl = dynamic_cast< LinePolyPolygon* >( xPoly.get() ); + + if( pPolyImpl ) + { + return pPolyImpl->getGraphicsPath( bNoLineJoin ); + } + else + { + return tools::graphicsPathFromB2DPolyPolygon( + polyPolygonFromXPolyPolygon2D( xPoly ), bNoLineJoin ); + } + } + + bool drawGdiPlusBitmap( const GraphicsSharedPtr& rGraphics, + const BitmapSharedPtr& rBitmap ) + { + Gdiplus::PointF aPoint; + return (Gdiplus::Ok == rGraphics->DrawImage( rBitmap.get(), + aPoint ) ); + } + + bool drawDIBits( const std::shared_ptr<Gdiplus::Graphics>& rGraphics, + const BITMAPINFO& rBI, + const void* pBits ) + { + BitmapSharedPtr pBitmap( + Gdiplus::Bitmap::FromBITMAPINFO( &rBI, + const_cast<void*>(pBits) ) ); + + return drawGdiPlusBitmap( rGraphics, + pBitmap ); + } + + bool drawRGBABits( const std::shared_ptr<Gdiplus::Graphics>& rGraphics, + const RawRGBABitmap& rRawRGBAData ) + { + BitmapSharedPtr pBitmap = std::make_shared<Gdiplus::Bitmap>( rRawRGBAData.mnWidth, + rRawRGBAData.mnHeight, + PixelFormat32bppARGB ); + + Gdiplus::BitmapData aBmpData; + aBmpData.Width = rRawRGBAData.mnWidth; + aBmpData.Height = rRawRGBAData.mnHeight; + aBmpData.Stride = 4*aBmpData.Width; // bottom-up format + aBmpData.PixelFormat = PixelFormat32bppARGB; + aBmpData.Scan0 = const_cast<sal_uInt8*>(rRawRGBAData.maBitmapData.data()); + + const Gdiplus::Rect aRect( 0,0,aBmpData.Width,aBmpData.Height ); + if( Gdiplus::Ok != pBitmap->LockBits( &aRect, + Gdiplus::ImageLockModeWrite | Gdiplus::ImageLockModeUserInputBuf, + PixelFormat32bppARGB, + &aBmpData ) ) + { + return false; + } + + // commit data to bitmap + pBitmap->UnlockBits( &aBmpData ); + + return drawGdiPlusBitmap( rGraphics, + pBitmap ); + } + + BitmapSharedPtr bitmapFromXBitmap( const uno::Reference< rendering::XBitmap >& xBitmap ) + { + BitmapProvider* pBitmapProvider = dynamic_cast< BitmapProvider* >(xBitmap.get()); + + if( pBitmapProvider ) + { + IBitmapSharedPtr pBitmap( pBitmapProvider->getBitmap() ); + return pBitmap->getBitmap(); + } + else + { + // not a native CanvasBitmap, extract VCL bitmap and + // render into GDI+ bitmap of similar size + // ================================================= + + const geometry::IntegerSize2D aBmpSize( xBitmap->getSize() ); + BitmapSharedPtr pBitmap; + + if( xBitmap->hasAlpha() ) + { + // TODO(P2): At least for the alpha bitmap case, it + // would be possible to generate the corresponding + // bitmap directly + pBitmap = std::make_shared<Gdiplus::Bitmap>( aBmpSize.Width, + aBmpSize.Height, + PixelFormat32bppARGB ); + } + else + { + // TODO(F2): Might be wise to create bitmap compatible + // to the VCL bitmap. Also, check whether the VCL + // bitmap's system handles can be used to create the + // GDI+ bitmap (currently, it does not seem so). + pBitmap = std::make_shared<Gdiplus::Bitmap>( aBmpSize.Width, + aBmpSize.Height, + PixelFormat24bppRGB ); + } + + GraphicsSharedPtr pGraphics(createGraphicsFromBitmap(pBitmap)); + tools::setupGraphics(*pGraphics); + if( !drawVCLBitmapFromXBitmap( + pGraphics, + xBitmap) ) + { + pBitmap.reset(); + } + + return pBitmap; + } + } + + CanvasFont::ImplRef canvasFontFromXFont( const uno::Reference< rendering::XCanvasFont >& xFont ) + { + CanvasFont* pCanvasFont = dynamic_cast< CanvasFont* >(xFont.get()); + + ENSURE_ARG_OR_THROW( pCanvasFont, + "canvasFontFromXFont(): Invalid XFont (or incompatible font for this XCanvas)" ); + + return CanvasFont::ImplRef( pCanvasFont ); + } + + void setModulateImageAttributes( Gdiplus::ImageAttributes& o_rAttr, + double nRedModulation, + double nGreenModulation, + double nBlueModulation, + double nAlphaModulation ) + { + // This gets rather verbose, but we have to setup a color + // transformation matrix, in order to incorporate the global + // alpha value mfAlpha into the bitmap rendering. + Gdiplus::ColorMatrix aColorMatrix; + + aColorMatrix.m[0][0] = static_cast<Gdiplus::REAL>(nRedModulation); + aColorMatrix.m[0][1] = 0.0; + aColorMatrix.m[0][2] = 0.0; + aColorMatrix.m[0][3] = 0.0; + aColorMatrix.m[0][4] = 0.0; + + aColorMatrix.m[1][0] = 0.0; + aColorMatrix.m[1][1] = static_cast<Gdiplus::REAL>(nGreenModulation); + aColorMatrix.m[1][2] = 0.0; + aColorMatrix.m[1][3] = 0.0; + aColorMatrix.m[1][4] = 0.0; + + aColorMatrix.m[2][0] = 0.0; + aColorMatrix.m[2][1] = 0.0; + aColorMatrix.m[2][2] = static_cast<Gdiplus::REAL>(nBlueModulation); + aColorMatrix.m[2][3] = 0.0; + aColorMatrix.m[2][4] = 0.0; + + aColorMatrix.m[3][0] = 0.0; + aColorMatrix.m[3][1] = 0.0; + aColorMatrix.m[3][2] = 0.0; + aColorMatrix.m[3][3] = static_cast<Gdiplus::REAL>(nAlphaModulation); + aColorMatrix.m[3][4] = 0.0; + + aColorMatrix.m[4][0] = 0.0; + aColorMatrix.m[4][1] = 0.0; + aColorMatrix.m[4][2] = 0.0; + aColorMatrix.m[4][3] = 0.0; + aColorMatrix.m[4][4] = 1.0; + + o_rAttr.SetColorMatrix( &aColorMatrix ); + } + +} // namespace dxcanvas::tools + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_impltools.hxx b/canvas/source/directx/dx_impltools.hxx new file mode 100644 index 0000000000..2c6b85ce83 --- /dev/null +++ b/canvas/source/directx/dx_impltools.hxx @@ -0,0 +1,124 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/uno/Sequence.hxx> +#include <com/sun/star/util/TriState.hpp> + +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/polygon/b2dpolypolygon.hxx> +#include <basegfx/numeric/ftools.hxx> + +#include <memory> +#include "dx_canvasfont.hxx" + +namespace basegfx +{ + class B2DPoint; + class B2DRange; + class B2DHomMatrix; + class B2IRange; + class B2DPolyPolygon; +}; + +namespace com::sun::star::geometry +{ + struct IntegerRectangle2D; + struct RealPoint2D; +} + +namespace com::sun::star::rendering +{ + class XBitmap; + class XPolyPolygon2D; + class XCanvasFont; +} + + +namespace dxcanvas::tools +{ + struct RawRGBABitmap; + + ::basegfx::B2DPolyPolygon + polyPolygonFromXPolyPolygon2D( const css::uno::Reference< css::rendering::XPolyPolygon2D >& ); + + Gdiplus::Graphics* createGraphicsFromHDC(HDC); + GraphicsSharedPtr createGraphicsFromBitmap(const BitmapSharedPtr&); + + void setupGraphics( Gdiplus::Graphics& rGraphics ); + + void gdiPlusMatrixFromB2DHomMatrix( Gdiplus::Matrix& rGdiplusMatrix, + const ::basegfx::B2DHomMatrix& rMatrix ); + void gdiPlusMatrixFromAffineMatrix2D( Gdiplus::Matrix& rGdiplusMatrix, + const css::geometry::AffineMatrix2D& rMatrix ); + + Gdiplus::PointF gdiPlusPointFFromRealPoint2D( const css::geometry::RealPoint2D& ); + Gdiplus::RectF gdiPlusRectFFromRectangle2D( const css::geometry::RealRectangle2D& ); + Gdiplus::Rect gdiPlusRectFromIntegerRectangle2D( const css::geometry::IntegerRectangle2D& ); + RECT gdiRectFromB2IRect( const ::basegfx::B2IRange& ); + + css::geometry::RealPoint2D realPoint2DFromGdiPlusPointF( const Gdiplus::PointF& ); + css::geometry::RealRectangle2D realRectangle2DFromGdiPlusRectF( const Gdiplus::RectF& ); + + ::basegfx::B2DPoint b2dPointFromGdiPlusPointF( const Gdiplus::PointF& ); + ::basegfx::B2DRange b2dRangeFromGdiPlusRectF( const Gdiplus::RectF& ); + + css::uno::Sequence< sal_Int8 > argbToIntSequence( Gdiplus::ARGB rColor ); + Gdiplus::ARGB sequenceToArgb( const css::uno::Sequence< sal_Int8 >& rColor ); + Gdiplus::ARGB sequenceToArgb( const css::uno::Sequence< double >& rColor ); + + GraphicsPathSharedPtr graphicsPathFromRealPoint2DSequence( const css::uno::Sequence< + css::uno::Sequence< css::geometry::RealPoint2D > >& ); + + GraphicsPathSharedPtr graphicsPathFromB2DPolygon( + const ::basegfx::B2DPolygon& rPoly, + bool bNoLineJoin = false); + + GraphicsPathSharedPtr graphicsPathFromB2DPolyPolygon( + const ::basegfx::B2DPolyPolygon& rPoly, + bool bNoLineJoin = false); + + GraphicsPathSharedPtr graphicsPathFromXPolyPolygon2D( + const css::uno::Reference< css::rendering::XPolyPolygon2D >&, + bool bNoLineJoin = false ); + + bool drawGdiPlusBitmap( const GraphicsSharedPtr& rGraphics, + const BitmapSharedPtr& rBitmap ); + bool drawDIBits( const std::shared_ptr< Gdiplus::Graphics >& rGraphics, + const BITMAPINFO& rBI, + const void* pBits ); + + bool drawRGBABits( const std::shared_ptr< Gdiplus::Graphics >& rGraphics, + const RawRGBABitmap& rRawRGBAData ); + + BitmapSharedPtr bitmapFromXBitmap( const css::uno::Reference< css::rendering::XBitmap >& xBitmap ); + + CanvasFont::ImplRef canvasFontFromXFont( const css::uno::Reference< css::rendering::XCanvasFont >& xFont ); + + void setModulateImageAttributes( Gdiplus::ImageAttributes& o_rAttr, + double nRedModulation, + double nGreenModulation, + double nBlueModulation, + double nAlphaModulation ); +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_linepolypolygon.cxx b/canvas/source/directx/dx_linepolypolygon.cxx new file mode 100644 index 0000000000..53ec2953e7 --- /dev/null +++ b/canvas/source/directx/dx_linepolypolygon.cxx @@ -0,0 +1,59 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <basegfx/utils/canvastools.hxx> + +#include "dx_linepolypolygon.hxx" + + +using namespace ::com::sun::star; + +namespace dxcanvas +{ + LinePolyPolygon::LinePolyPolygon( const ::basegfx::B2DPolyPolygon& rPolyPoly ) : + ::basegfx::unotools::UnoPolyPolygon( rPolyPoly ), + mpGdiPlusUser( GDIPlusUser::createInstance() ), + mpPath() + { + } + + GraphicsPathSharedPtr LinePolyPolygon::getGraphicsPath( bool bNoLineJoin ) const + { + // generate GraphicsPath only on demand (gets deleted as soon + // as any of the modifying methods above touches the + // B2DPolyPolygon). + if( !mpPath ) + { + mpPath = tools::graphicsPathFromB2DPolyPolygon( getPolyPolygonUnsafe(), bNoLineJoin ); + mpPath->SetFillMode( const_cast<LinePolyPolygon*>(this)->getFillRule() == rendering::FillRule_EVEN_ODD ? + Gdiplus::FillModeAlternate : Gdiplus::FillModeWinding ); + } + + return mpPath; + } + + void LinePolyPolygon::modifying() const + { + mpPath.reset(); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_linepolypolygon.hxx b/canvas/source/directx/dx_linepolypolygon.hxx new file mode 100644 index 0000000000..eaec483a02 --- /dev/null +++ b/canvas/source/directx/dx_linepolypolygon.hxx @@ -0,0 +1,47 @@ +/* -*- 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 . + */ + +#pragma once + +#include <canvas/canvastools.hxx> +#include <basegfx/utils/unopolypolygon.hxx> + +#include "dx_gdiplususer.hxx" +#include "dx_impltools.hxx" + + +namespace dxcanvas +{ + class LinePolyPolygon : public ::basegfx::unotools::UnoPolyPolygon + { + public: + explicit LinePolyPolygon( const ::basegfx::B2DPolyPolygon& ); + + GraphicsPathSharedPtr getGraphicsPath( bool bNoLineJoin = false) const; + + private: + // overridden, to clear mpPath + virtual void modifying() const override; + + GDIPlusUserSharedPtr mpGdiPlusUser; + mutable GraphicsPathSharedPtr mpPath; + }; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_rendermodule.hxx b/canvas/source/directx/dx_rendermodule.hxx new file mode 100644 index 0000000000..4b13937967 --- /dev/null +++ b/canvas/source/directx/dx_rendermodule.hxx @@ -0,0 +1,80 @@ +/* -*- 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 . + */ + +#pragma once + +#include <basegfx/vector/b2ivector.hxx> +#include <basegfx/range/b2irectangle.hxx> +#include <rendering/irendermodule.hxx> +#include <memory> +#include "dx_winstuff.hxx" + +namespace vcl { class Window; } +namespace basegfx +{ + class B2IRange; +} + +namespace dxcanvas +{ + /// Specialization of IRenderModule for DirectX + struct IDXRenderModule : public canvas::IRenderModule + { + /** Flip front- and backbuffer, update only given area + + Note: Both update area and offset are ignored for + fullscreen canvas, that uses page flipping (cannot, by + definition, do anything else there except displaying the + full backbuffer instead of the front buffer) + + @param rUpdateArea + Area to copy from backbuffer to front + + @param rCurrWindowArea + Current area of VCL window (coordinates relative to VCL + HWND) + */ + virtual bool flip( const ::basegfx::B2IRectangle& rUpdateArea, + const ::basegfx::B2IRectangle& rCurrWindowArea ) = 0; + + /** Resize backbuffer area for this render module + */ + virtual void resize( const ::basegfx::B2IRange& rect ) = 0; + + /// Write a snapshot of the screen to disk + virtual void screenShot() = 0; + + virtual sal::systools::COMReference<surface_type> + createSystemMemorySurface( + const ::basegfx::B2ISize& rSize) = 0; + + virtual void disposing() = 0; + virtual HWND getHWND() const = 0; + }; + + typedef std::shared_ptr< IDXRenderModule > IDXRenderModuleSharedPtr; + + + /** Factory method, to create an IRenderModule instance for the + given VCL window instance + */ + IDXRenderModuleSharedPtr createRenderModule( const vcl::Window& rParent ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_sprite.hxx b/canvas/source/directx/dx_sprite.hxx new file mode 100644 index 0000000000..c1f75e1b1e --- /dev/null +++ b/canvas/source/directx/dx_sprite.hxx @@ -0,0 +1,45 @@ +/* -*- 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 . + */ + +#pragma once + +#include <base/sprite.hxx> + +namespace dxcanvas +{ + /** Specialization of ::canvas::Sprite interface, to also provide + redraw methods. + */ + class Sprite : public ::canvas::Sprite + { + public: + + /** Redraw sprite using the hardware + + This method will silently fail, if the previous + restoreTextures() call failed. + */ + virtual void redraw() const = 0; + + protected: + ~Sprite() {} + }; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_spritecanvas.cxx b/canvas/source/directx/dx_spritecanvas.cxx new file mode 100644 index 0000000000..f4fe39203f --- /dev/null +++ b/canvas/source/directx/dx_spritecanvas.cxx @@ -0,0 +1,192 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> +#include <sal/log.hxx> + +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/numeric/ftools.hxx> +#include <basegfx/point/b2dpoint.hxx> +#include <basegfx/utils/canvastools.hxx> +#include <com/sun/star/lang/NoSupportException.hpp> +#include <com/sun/star/lang/XSingleServiceFactory.hpp> +#include <com/sun/star/registry/XRegistryKey.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <cppuhelper/supportsservice.hxx> +#include <osl/mutex.hxx> +#include <toolkit/helper/vclunohelper.hxx> +#include <comphelper/diagnose_ex.hxx> + +#include <canvas/canvastools.hxx> + +#include "dx_spritecanvas.hxx" +#include "dx_winstuff.hxx" + + +using namespace ::com::sun::star; + +namespace dxcanvas +{ + SpriteCanvas::SpriteCanvas( const uno::Sequence< uno::Any >& aArguments, + const uno::Reference< uno::XComponentContext >& rxContext ) : + maArguments(aArguments), + mxComponentContext( rxContext ) + { + } + + void SpriteCanvas::initialize() + { + // #i64742# Only call initialize when not in probe mode + if( maArguments.getLength() == 0 ) + return; + + SAL_INFO("canvas.directx", "SpriteCanvas::initialize called" ); + + /* aArguments: + 0: ptr to creating instance (Window or VirtualDevice) + 1: SystemEnvData as a streamed Any (or empty for VirtualDevice) + 2: current bounds of creating instance + 3: bool, denoting always on top state for Window (always false for VirtualDevice) + 4: XWindow for creating Window (or empty for VirtualDevice) + 5: SystemGraphicsData as a streamed Any + */ + ENSURE_ARG_OR_THROW( maArguments.getLength() >= 4 && + maArguments[3].getValueTypeClass() == uno::TypeClass_INTERFACE, + "VCLSpriteCanvas::initialize: wrong number of arguments, or wrong types" ); + + uno::Reference< awt::XWindow > xParentWindow; + maArguments[3] >>= xParentWindow; + auto pParentWindow = VCLUnoHelper::GetWindow(xParentWindow); + if( !pParentWindow ) + throw lang::NoSupportException( "Parent window not VCL window, or canvas out-of-process!" ); + + awt::Rectangle aRect; + maArguments[1] >>= aRect; + + bool bIsFullscreen( false ); + maArguments[2] >>= bIsFullscreen; + + // setup helper + maDeviceHelper.init( *pParentWindow, + *this, + aRect, + bIsFullscreen ); + maCanvasHelper.init( *this, + maRedrawManager, + maDeviceHelper.getRenderModule(), + maDeviceHelper.getSurfaceProxy(), + maDeviceHelper.getBackBuffer(), + ::basegfx::B2ISize() ); + maArguments.realloc(0); + } + + void SpriteCanvas::disposeThis() + { + ::osl::MutexGuard aGuard( m_aMutex ); + + mxComponentContext.clear(); + + // forward to parent + SpriteCanvasBaseT::disposeThis(); + } + + sal_Bool SAL_CALL SpriteCanvas::showBuffer( sal_Bool bUpdateAll ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + + // avoid repaints on hidden window (hidden: not mapped to + // screen). Return failure, since the screen really has _not_ + // been updated (caller should try again later) + return mbIsVisible && SpriteCanvasBaseT::showBuffer( bUpdateAll ); + } + + sal_Bool SAL_CALL SpriteCanvas::switchBuffer( sal_Bool bUpdateAll ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + + // avoid repaints on hidden window (hidden: not mapped to + // screen). Return failure, since the screen really has _not_ + // been updated (caller should try again later) + return mbIsVisible && SpriteCanvasBaseT::switchBuffer( bUpdateAll ); + } + + sal_Bool SAL_CALL SpriteCanvas::updateScreen( sal_Bool bUpdateAll ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + + // avoid repaints on hidden window (hidden: not mapped to + // screen). Return failure, since the screen really has _not_ + // been updated (caller should try again later) + return mbIsVisible && maCanvasHelper.updateScreen( + ::basegfx::unotools::b2IRectangleFromAwtRectangle(maBounds), + bUpdateAll, + mbSurfaceDirty ); + } + + OUString SAL_CALL SpriteCanvas::getServiceName( ) + { + return "com.sun.star.rendering.SpriteCanvas.DX9"; + } + + // XServiceInfo + css::uno::Sequence<OUString> SpriteCanvas::getSupportedServiceNames( ) + { + return { "com.sun.star.rendering.SpriteCanvas.DX9" }; + } + OUString SpriteCanvas::getImplementationName( ) + { + return "com.sun.star.comp.rendering.SpriteCanvas.DX9"; + } + sal_Bool SpriteCanvas::supportsService( const OUString& sServiceName ) + { + return cppu::supportsService(this, sServiceName); + } + + const IDXRenderModuleSharedPtr& SpriteCanvas::getRenderModule() const + { + ::osl::MutexGuard aGuard( m_aMutex ); + + return maDeviceHelper.getRenderModule(); + } + + const DXSurfaceBitmapSharedPtr& SpriteCanvas::getBackBuffer() const + { + ::osl::MutexGuard aGuard( m_aMutex ); + + return maDeviceHelper.getBackBuffer(); + } + + IBitmapSharedPtr SpriteCanvas::getBitmap() const + { + return maDeviceHelper.getBackBuffer(); + } + + extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* + canvas_directx9_SpriteCanvas_get_implementation( + css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const& args) + { + rtl::Reference<SpriteCanvas> xCanvas(new SpriteCanvas(args, context)); + xCanvas->initialize(); + return cppu::acquire(xCanvas.get()); + } + +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_spritecanvas.hxx b/canvas/source/directx/dx_spritecanvas.hxx new file mode 100644 index 0000000000..081337f72c --- /dev/null +++ b/canvas/source/directx/dx_spritecanvas.hxx @@ -0,0 +1,155 @@ +/* -*- 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 . + */ + +#pragma once + +#include <rtl/ref.hxx> + +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/lang/XServiceName.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/awt/XWindowListener.hpp> +#include <com/sun/star/awt/XTopWindow.hpp> +#include <com/sun/star/util/XUpdatable.hpp> +#include <com/sun/star/rendering/XSpriteCanvas.hpp> +#include <com/sun/star/rendering/XIntegerBitmap.hpp> +#include <com/sun/star/rendering/XGraphicDevice.hpp> +#include <com/sun/star/rendering/XBufferController.hpp> + +#include <cppuhelper/compbase.hxx> +#include <comphelper/uno3.hxx> + +#include <base/spritecanvasbase.hxx> +#include <base/spritesurface.hxx> +#include <base/disambiguationhelper.hxx> +#include <base/bufferedgraphicdevicebase.hxx> + +#include "dx_bitmapprovider.hxx" +#include "dx_spritecanvashelper.hxx" +#include "dx_surfacebitmap.hxx" +#include "dx_impltools.hxx" +#include "dx_spritedevicehelper.hxx" + + +namespace dxcanvas +{ + typedef ::cppu::WeakComponentImplHelper< css::rendering::XSpriteCanvas, + css::rendering::XIntegerBitmap, + css::rendering::XGraphicDevice, + css::lang::XMultiServiceFactory, + css::rendering::XBufferController, + css::awt::XWindowListener, + css::util::XUpdatable, + css::beans::XPropertySet, + css::lang::XServiceName, + css::lang::XServiceInfo> WindowGraphicDeviceBase_Base; + typedef ::canvas::BufferedGraphicDeviceBase< ::canvas::DisambiguationHelper< WindowGraphicDeviceBase_Base >, + SpriteDeviceHelper, + ::osl::MutexGuard, + ::cppu::OWeakObject > SpriteCanvasBase_Base; + /** Mixin SpriteSurface + + Have to mixin the SpriteSurface before deriving from + ::canvas::SpriteCanvasBase, as this template should already + implement some of those interface methods. + + The reason why this appears kinda convoluted is the fact that + we cannot specify non-IDL types as WeakComponentImplHelper + template args, and furthermore, don't want to derive + ::canvas::SpriteCanvasBase directly from + ::canvas::SpriteSurface (because derivees of + ::canvas::SpriteCanvasBase have to explicitly forward the + XInterface methods (e.g. via DECLARE_UNO3_AGG_DEFAULTS) + anyway). Basically, ::canvas::CanvasCustomSpriteBase should + remain a base class that provides implementation, not to + enforce any specific interface on its derivees. + */ + class SpriteCanvasBaseSpriteSurface_Base : public SpriteCanvasBase_Base, + public ::canvas::SpriteSurface + { + }; + + typedef ::canvas::SpriteCanvasBase< SpriteCanvasBaseSpriteSurface_Base, + SpriteCanvasHelper, + ::osl::MutexGuard, + ::cppu::OWeakObject > SpriteCanvasBaseT; + + /** Product of this component's factory. + + The SpriteCanvas object combines the actual Window canvas with + the XGraphicDevice interface. This is because there's a + one-to-one relation between them, anyway, since each window + can have exactly one canvas and one associated + XGraphicDevice. And to avoid messing around with circular + references, this is implemented as one single object. + */ + class SpriteCanvas : public SpriteCanvasBaseT, public BitmapProvider + { + public: + SpriteCanvas( const css::uno::Sequence< + css::uno::Any >& aArguments, + const css::uno::Reference< + css::uno::XComponentContext >& rxContext ); + + void initialize(); + + /// Dispose all internal references + virtual void disposeThis() override; + + // Forwarding the XComponent implementation to the + // cppu::ImplHelper templated base + // Classname Base doing refcounting Base implementing the XComponent interface + // | | | + // V V V + DECLARE_UNO3_XCOMPONENT_AGG_DEFAULTS( SpriteCanvas, WindowGraphicDeviceBase_Base, ::cppu::WeakComponentImplHelperBase ) + + // XBufferController (partial) + virtual sal_Bool SAL_CALL showBuffer( sal_Bool bUpdateAll ) override; + virtual sal_Bool SAL_CALL switchBuffer( sal_Bool bUpdateAll ) override; + + // XSpriteCanvas (partial) + virtual sal_Bool SAL_CALL updateScreen( sal_Bool bUpdateAll ) override; + + // XServiceName + virtual OUString SAL_CALL getServiceName( ) override; + + // XServiceInfo + virtual css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames( ) override; + virtual OUString SAL_CALL getImplementationName( ) override; + virtual sal_Bool SAL_CALL supportsService( const OUString& ) override; + + /// Retrieve rendermodule object for this Canvas + const IDXRenderModuleSharedPtr& getRenderModule() const; + + /// Get backbuffer for this canvas + const DXSurfaceBitmapSharedPtr& getBackBuffer() const; + + // BitmapProvider + virtual IBitmapSharedPtr getBitmap() const override; + + private: + css::uno::Sequence< css::uno::Any > maArguments; + css::uno::Reference< css::uno::XComponentContext > mxComponentContext; + }; + + typedef ::rtl::Reference< SpriteCanvas > SpriteCanvasRef; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_spritecanvashelper.cxx b/canvas/source/directx/dx_spritecanvashelper.cxx new file mode 100644 index 0000000000..d676fa2a80 --- /dev/null +++ b/canvas/source/directx/dx_spritecanvashelper.cxx @@ -0,0 +1,352 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <boost/cast.hpp> + +#include <basegfx/range/b2drectangle.hxx> +#include <basegfx/utils/canvastools.hxx> +#include <comphelper/scopeguard.hxx> +#include <comphelper/diagnose_ex.hxx> + +#include <canvas/canvastools.hxx> + +#include "dx_canvascustomsprite.hxx" +#include "dx_spritecanvashelper.hxx" + +using namespace ::com::sun::star; + +namespace dxcanvas +{ + namespace + { + void repaintBackground( const ::basegfx::B2DRange& rUpdateArea, + const ::basegfx::B2IRange& rOutputArea, + const DXSurfaceBitmapSharedPtr& rBackBuffer ) + { + // TODO(E1): Use numeric_cast to catch overflow here + ::basegfx::B2IRange aActualArea( 0, 0, + static_cast<sal_Int32>(rOutputArea.getWidth()), + static_cast<sal_Int32>(rOutputArea.getHeight()) ); + aActualArea.intersect( fround( rUpdateArea ) ); + + // repaint the given area of the screen with background content + rBackBuffer->draw(aActualArea); + } + + void spriteRedraw( const ::canvas::Sprite::Reference& rSprite ) + { + // downcast to derived dxcanvas::Sprite interface, which + // provides the actual redraw methods. + ::boost::polymorphic_downcast< Sprite* >( + rSprite.get() )->redraw(); + } + } + + SpriteCanvasHelper::SpriteCanvasHelper() : + mpSpriteSurface( nullptr ), + mpRedrawManager( nullptr ), + mpRenderModule(), + mpSurfaceProxy(), + mpBackBuffer(), + maUpdateRect(), + maScrapRect(), + mbShowSpriteBounds( false ) + { +#if OSL_DEBUG_LEVEL > 0 + // inverse default for verbose debug mode + mbShowSpriteBounds = true; +#endif + } + + void SpriteCanvasHelper::init( SpriteCanvas& rParent, + ::canvas::SpriteRedrawManager& rManager, + const IDXRenderModuleSharedPtr& rRenderModule, + const std::shared_ptr<canvas::ISurfaceProxyManager>& rSurfaceProxy, + const DXSurfaceBitmapSharedPtr& rBackBuffer, + const ::basegfx::B2ISize& rOutputOffset ) + { + // init base + setDevice( rParent ); + setTarget( rBackBuffer, rOutputOffset ); + + mpSpriteSurface = &rParent; + mpRedrawManager = &rManager; + mpRenderModule = rRenderModule; + mpSurfaceProxy = rSurfaceProxy; + mpBackBuffer = rBackBuffer; + } + + void SpriteCanvasHelper::disposing() + { + if(mpRenderModule) + mpRenderModule->disposing(); + + mpBackBuffer.reset(); + mpRenderModule.reset(); + mpRedrawManager = nullptr; + mpSpriteSurface = nullptr; + + // forward to base + CanvasHelper::disposing(); + } + + uno::Reference< rendering::XAnimatedSprite > SpriteCanvasHelper::createSpriteFromAnimation( + const uno::Reference< rendering::XAnimation >& /*animation*/ ) + { + return uno::Reference< rendering::XAnimatedSprite >(); + } + + uno::Reference< rendering::XAnimatedSprite > SpriteCanvasHelper::createSpriteFromBitmaps( + const uno::Sequence< uno::Reference< rendering::XBitmap > >& /*animationBitmaps*/, + sal_Int8 /*interpolationMode*/ ) + { + return uno::Reference< rendering::XAnimatedSprite >(); + } + + uno::Reference< rendering::XCustomSprite > SpriteCanvasHelper::createCustomSprite( const geometry::RealSize2D& spriteSize ) + { + if( !mpRedrawManager ) + return uno::Reference< rendering::XCustomSprite >(); // we're disposed + + return uno::Reference< rendering::XCustomSprite >( + new CanvasCustomSprite( spriteSize, + mpSpriteSurface, + mpRenderModule, + mpSurfaceProxy, + mbShowSpriteBounds ) ); + } + + uno::Reference< rendering::XSprite > SpriteCanvasHelper::createClonedSprite( const uno::Reference< rendering::XSprite >& /*original*/ ) + { + return uno::Reference< rendering::XSprite >(); + } + + bool SpriteCanvasHelper::updateScreen( const ::basegfx::B2IRectangle& rCurrArea, + bool bUpdateAll, + bool& io_bSurfaceDirty ) + { + if( !mpRedrawManager || + !mpRenderModule || + !mpBackBuffer ) + { + return false; // disposed, or otherwise dysfunctional + } + + // store current output area (need to tunnel that to the + // background, scroll, opaque and general sprite repaint + // routines) + maScrapRect = rCurrArea; + + // clear area that needs to be blitted to screen beforehand + maUpdateRect.reset(); + + // TODO(P1): Might be worthwhile to track areas of background + // changes, too. + + // TODO(P2): Might be worthwhile to use page-flipping only if + // a certain percentage of screen area has changed - and + // compose directly to the front buffer otherwise. + if( !bUpdateAll && !io_bSurfaceDirty ) + { + // background has not changed, so we're free to optimize + // repaint to areas where a sprite has changed + + // process each independent area of overlapping sprites + // separately. + mpRedrawManager->forEachSpriteArea( *this ); + + // flip primary surface to screen + // ============================== + + // perform buffer flipping + mpRenderModule->flip( maUpdateRect, + rCurrArea ); + } + else + { + // limit update to parent window area (ignored for fullscreen) + // TODO(E1): Use numeric_cast to catch overflow here + const ::basegfx::B2IRectangle aUpdateArea( 0,0, + static_cast<sal_Int32>(rCurrArea.getWidth()), + static_cast<sal_Int32>(rCurrArea.getHeight()) ); + + // background has changed, or called requested full + // update, or we're performing double buffering via page + // flipping, so we currently have no choice but repaint + // everything + + // repaint the whole screen with background content + mpBackBuffer->draw(aUpdateArea); + + // redraw sprites + mpRedrawManager->forEachSprite( &spriteRedraw ); + + // flip primary surface to screen + // ============================== + + // perform buffer flipping + mpRenderModule->flip( aUpdateArea, + rCurrArea ); + } + + // change record vector must be cleared, for the next turn of + // rendering and sprite changing + mpRedrawManager->clearChangeRecords(); + + io_bSurfaceDirty = false; + + return true; + } + + void SpriteCanvasHelper::backgroundPaint( const ::basegfx::B2DRange& rUpdateRect ) + { + ENSURE_OR_THROW( mpRenderModule && + mpBackBuffer, + "SpriteCanvasHelper::backgroundPaint(): NULL device pointer " ); + + repaintBackground( rUpdateRect, + maScrapRect, + mpBackBuffer ); + } + + void SpriteCanvasHelper::scrollUpdate( const ::basegfx::B2DRange& /*rMoveStart*/, + const ::basegfx::B2DRange& rMoveEnd, + const ::canvas::SpriteRedrawManager::UpdateArea& rUpdateArea ) + { + ENSURE_OR_THROW( mpRenderModule && + mpBackBuffer, + "SpriteCanvasHelper::scrollUpdate(): NULL device pointer " ); + + // round rectangles to integer pixel. Note: have to be + // extremely careful here, to avoid off-by-one errors for + // the destination area: otherwise, the next scroll update + // would copy pixel that are not supposed to be part of + // the sprite. + const ::basegfx::B2IRange& rDestRect( + ::canvas::tools::spritePixelAreaFromB2DRange( rMoveEnd ) ); + + // not much sense in really implementing scrollUpdate here, + // since outputting a sprite only partially would result in + // expensive clipping. Furthermore, we cannot currently render + // 3D directly to the front buffer, thus, would have to blit + // the full sprite area, anyway. But at least optimized in the + // sense that unnecessary background paints behind the sprites + // are avoided. + for( const auto& rComponent : rUpdateArea.maComponentList ) + { + const ::canvas::Sprite::Reference& rSprite( rComponent.second.getSprite() ); + + if( rSprite.is() ) + { + // downcast to derived dxcanvas::Sprite interface, which + // provides the actual redraw methods. + ::boost::polymorphic_downcast< Sprite* >( rSprite.get() )->redraw(); + } + } + + // repaint uncovered areas from backbuffer - take the + // _rounded_ rectangles from above, to have the update + // consistent with the scroll above. + std::vector< ::basegfx::B2DRange > aUncoveredAreas; + ::basegfx::computeSetDifference( aUncoveredAreas, + rUpdateArea.maTotalBounds, + ::basegfx::B2DRange( rDestRect ) ); + for( const auto& rUncoveredArea : aUncoveredAreas ) + repaintBackground( rUncoveredArea, maScrapRect, mpBackBuffer ); + + // TODO(E1): Use numeric_cast to catch overflow here + ::basegfx::B2IRange aActualArea( 0, 0, + static_cast<sal_Int32>(maScrapRect.getWidth()), + static_cast<sal_Int32>(maScrapRect.getHeight()) ); + aActualArea.intersect( fround( rUpdateArea.maTotalBounds ) ); + + // add given update area to the 'blit to foreground' rect + maUpdateRect.expand( aActualArea ); + } + + void SpriteCanvasHelper::opaqueUpdate( const ::basegfx::B2DRange& rTotalArea, + const std::vector< ::canvas::Sprite::Reference >& rSortedUpdateSprites ) + { + ENSURE_OR_THROW( mpRenderModule && + mpBackBuffer, + "SpriteCanvasHelper::opaqueUpdate(): NULL device pointer " ); + + // TODO(P2): optimize this by truly rendering to the front + // buffer. Currently, we've the 3D device only for the back + // buffer. + for( const auto& rSprite : rSortedUpdateSprites ) + { + if( rSprite.is() ) + { + // downcast to derived dxcanvas::Sprite interface, which + // provides the actual redraw methods. + ::boost::polymorphic_downcast< Sprite* >( rSprite.get() )->redraw(); + } + } + + // TODO(E1): Use numeric_cast to catch overflow here + ::basegfx::B2IRange aActualArea( 0, 0, + static_cast<sal_Int32>(maScrapRect.getWidth()), + static_cast<sal_Int32>(maScrapRect.getHeight()) ); + aActualArea.intersect( fround( rTotalArea ) ); + + // add given update area to the 'blit to foreground' rect + maUpdateRect.expand( aActualArea ); + } + + void SpriteCanvasHelper::genericUpdate( const ::basegfx::B2DRange& rTotalArea, + const std::vector< ::canvas::Sprite::Reference >& rSortedUpdateSprites ) + { + ENSURE_OR_THROW( mpRenderModule && + mpBackBuffer, + "SpriteCanvasHelper::genericUpdate(): NULL device pointer " ); + + // paint background + // ================ + + // TODO(E1): Use numeric_cast to catch overflow here + ::basegfx::B2IRange aActualArea( 0, 0, + static_cast<sal_Int32>(maScrapRect.getWidth()), + static_cast<sal_Int32>(maScrapRect.getHeight()) ); + aActualArea.intersect( fround( rTotalArea ) ); + + // repaint the given area of the screen with background content + mpBackBuffer->draw(aActualArea); + + // paint sprite + // ============ + + for( const auto& rSprite : rSortedUpdateSprites ) + { + if( rSprite.is() ) + { + // downcast to derived dxcanvas::Sprite interface, which + // provides the actual redraw methods. + ::boost::polymorphic_downcast< Sprite* >( rSprite.get() )->redraw(); + } + } + + // add given update area to the 'blit to foreground' rect + maUpdateRect.expand( aActualArea ); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_spritecanvashelper.hxx b/canvas/source/directx/dx_spritecanvashelper.hxx new file mode 100644 index 0000000000..5c2d98ba81 --- /dev/null +++ b/canvas/source/directx/dx_spritecanvashelper.hxx @@ -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 . + */ + +#pragma once + +#include <com/sun/star/rendering/XSpriteCanvas.hpp> +#include <com/sun/star/rendering/XIntegerBitmap.hpp> + +#include <spriteredrawmanager.hxx> +#include <rendering/isurfaceproxy.hxx> +#include <rendering/isurfaceproxymanager.hxx> + +#include "dx_bitmapcanvashelper.hxx" +#include "dx_impltools.hxx" +#include "dx_rendermodule.hxx" +#include "dx_surfacebitmap.hxx" + +#include <basegfx/range/b2irectangle.hxx> + +namespace dxcanvas +{ + class SpriteCanvas; + + class SpriteCanvasHelper : public BitmapCanvasHelper + { + public: + SpriteCanvasHelper(); + + void init( SpriteCanvas& rParent, + ::canvas::SpriteRedrawManager& rManager, + const IDXRenderModuleSharedPtr& rRenderModule, + const std::shared_ptr<canvas::ISurfaceProxyManager>& rSurfaceProxy, + const DXSurfaceBitmapSharedPtr& rBackBuffer, + const ::basegfx::B2ISize& rOutputOffset ); + + /// Dispose all internal references + void disposing(); + + // XSpriteCanvas + css::uno::Reference< + css::rendering::XAnimatedSprite > createSpriteFromAnimation( + const css::uno::Reference< css::rendering::XAnimation >& animation ); + + css::uno::Reference< + css::rendering::XAnimatedSprite > createSpriteFromBitmaps( + const css::uno::Sequence< + css::uno::Reference< + css::rendering::XBitmap > >& animationBitmaps, + sal_Int8 interpolationMode ); + + css::uno::Reference< + css::rendering::XCustomSprite > createCustomSprite( + const css::geometry::RealSize2D& spriteSize ); + + css::uno::Reference< + css::rendering::XSprite > createClonedSprite( + const css::uno::Reference< css::rendering::XSprite >& original ); + + /** Actually perform the screen update + + @param rCurrArea + Current window area in absolute screen coordinates + + @param bUpdateAll + sal_True, if everything must be updated, not only changed + sprites + + @param io_bSurfaceDirty + In/out parameter, whether backbuffer surface is dirty (if + yes, we're performing a full update, anyway) + */ + bool updateScreen( const ::basegfx::B2IRectangle& rCurrArea, + bool bUpdateAll, + bool& io_bSurfaceDirty ); + + + // SpriteRedrawManager functor calls + + + /** Gets called for simple background repaints + */ + void backgroundPaint( const ::basegfx::B2DRange& rUpdateRect ); + + /** Gets called when area can be handled by scrolling. + + Called method must copy screen content from rMoveStart to + rMoveEnd, and restore the background in the uncovered + areas. + + @param rMoveStart + Source rect of the scroll + + @param rMoveEnd + Dest rect of the scroll + + @param rUpdateArea + All info necessary, should rMoveStart be partially or + fully outside the outdev + */ + void scrollUpdate( const ::basegfx::B2DRange& rMoveStart, + const ::basegfx::B2DRange& rMoveEnd, + const ::canvas::SpriteRedrawManager::UpdateArea& rUpdateArea ); + + void opaqueUpdate( const ::basegfx::B2DRange& rTotalArea, + const std::vector< ::canvas::Sprite::Reference >& rSortedUpdateSprites ); + + void genericUpdate( const ::basegfx::B2DRange& rTotalArea, + const std::vector< ::canvas::Sprite::Reference >& rSortedUpdateSprites ); + + private: + /// For generating sprites + SpriteCanvas* mpSpriteSurface; + + /// Set from the SpriteCanvas: instance coordinating sprite redraw + ::canvas::SpriteRedrawManager* mpRedrawManager; + + /// DX device, handling all low-level rendering + IDXRenderModuleSharedPtr mpRenderModule; + + std::shared_ptr<canvas::ISurfaceProxyManager> mpSurfaceProxy; + + /// Backbuffer, contains the static canvas render output + DXSurfaceBitmapSharedPtr mpBackBuffer; + + /// Completely temporary rect storage (used by sprite repaint) + mutable ::basegfx::B2IRange maUpdateRect; + + /// Completely temporary rect storage (used by sprite repaint) + mutable ::basegfx::B2IRange maScrapRect; + + /// When true, show small bound rects around each sprite + bool mbShowSpriteBounds; + }; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_spritedevicehelper.cxx b/canvas/source/directx/dx_spritedevicehelper.cxx new file mode 100644 index 0000000000..622246bc2a --- /dev/null +++ b/canvas/source/directx/dx_spritedevicehelper.cxx @@ -0,0 +1,221 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <memory> + +#include <sal/log.hxx> + +#include <basegfx/utils/canvastools.hxx> +#include <canvas/canvastools.hxx> +#include <com/sun/star/lang/NoSupportException.hpp> +#include <toolkit/helper/vclunohelper.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <vcl/sysdata.hxx> +#include <vcl/window.hxx> + +#include "dx_canvasbitmap.hxx" +#include "dx_linepolypolygon.hxx" +#include "dx_spritecanvas.hxx" +#include "dx_spritedevicehelper.hxx" +#include "dx_winstuff.hxx" + + +using namespace ::com::sun::star; + +namespace dxcanvas +{ + SpriteDeviceHelper::SpriteDeviceHelper() : + DeviceHelper(), + mpSpriteCanvas( nullptr ), + mpBackBuffer(), + mpSurfaceProxyManager(), + mpRenderModule() + { + } + + void SpriteDeviceHelper::init( vcl::Window& rWindow, + SpriteCanvas& rSpriteCanvas, + const awt::Rectangle& rRect, + bool /*bFullscreen*/ ) + { + // #i60490# ensure backbuffer has sensible minimal size + const sal_Int32 w( std::max(sal_Int32(1),sal_Int32(rRect.Width))); + const sal_Int32 h( std::max(sal_Int32(1),sal_Int32(rRect.Height))); + + rSpriteCanvas.setWindow( + uno::Reference<awt::XWindow2>( + VCLUnoHelper::GetInterface(&rWindow), + uno::UNO_QUERY_THROW) ); + + const SystemEnvData *pData = rWindow.GetSystemData(); + const HWND hWnd = reinterpret_cast<HWND>(pData->hWnd); + if( !IsWindow( hWnd ) ) + throw lang::NoSupportException( "Passed window has invalid system window, or canvas out-of-process!" ); + + mpSpriteCanvas = &rSpriteCanvas; + + try + { + // setup directx rendermodule + mpRenderModule = createRenderModule( rWindow ); + } + catch (...) { + + throw lang::NoSupportException( "Could not create DirectX device!", + rSpriteCanvas.getXWeak() ); + } + + // create the surfaceproxy manager + mpSurfaceProxyManager = ::canvas::createSurfaceProxyManager( mpRenderModule ); + + // #i60490# ensure backbuffer has sensible minimal size + mpBackBuffer = std::make_shared<DXSurfaceBitmap>( + basegfx::B2ISize(w,h), + mpSurfaceProxyManager, + mpRenderModule, + false); + + // Assumes: SystemChildWindow() has CS_OWNDC + DeviceHelper::init(GetDC(mpRenderModule->getHWND()),rWindow.GetOutDev(), rSpriteCanvas); + } + + void SpriteDeviceHelper::disposing() + { + // release all references + mpBackBuffer.reset(); + mpSurfaceProxyManager.reset(); + mpRenderModule.reset(); + mpSpriteCanvas = nullptr; + + DeviceHelper::disposing(); + } + + uno::Reference< rendering::XBitmap > SpriteDeviceHelper::createCompatibleBitmap( + const uno::Reference< rendering::XGraphicDevice >& /*rDevice*/, + const geometry::IntegerSize2D& size ) + { + if( !getDevice() ) + return uno::Reference< rendering::XBitmap >(); // we're disposed + + DXSurfaceBitmapSharedPtr pBitmap = std::make_shared<DXSurfaceBitmap>( + ::basegfx::unotools::b2ISizeFromIntegerSize2D(size), + mpSurfaceProxyManager, + mpRenderModule, + false); + + // create a 24bit RGB system memory surface + return uno::Reference< rendering::XBitmap >(new CanvasBitmap(pBitmap,getDevice())); + } + + uno::Reference< rendering::XVolatileBitmap > SpriteDeviceHelper::createVolatileBitmap( + const uno::Reference< rendering::XGraphicDevice >& /*rDevice*/, + const geometry::IntegerSize2D& /*size*/ ) + { + return uno::Reference< rendering::XVolatileBitmap >(); + } + + uno::Reference< rendering::XBitmap > SpriteDeviceHelper::createCompatibleAlphaBitmap( + const uno::Reference< rendering::XGraphicDevice >& /*rDevice*/, + const geometry::IntegerSize2D& size ) + { + if( !getDevice() ) + return uno::Reference< rendering::XBitmap >(); // we're disposed + + DXSurfaceBitmapSharedPtr pBitmap = std::make_shared<DXSurfaceBitmap>( + ::basegfx::unotools::b2ISizeFromIntegerSize2D(size), + mpSurfaceProxyManager, + mpRenderModule, + true); + + // create a 32bit ARGB system memory surface + return uno::Reference< rendering::XBitmap >(new CanvasBitmap(pBitmap,getDevice())); + } + + uno::Reference< rendering::XVolatileBitmap > SpriteDeviceHelper::createVolatileAlphaBitmap( + const uno::Reference< rendering::XGraphicDevice >& /*rDevice*/, + const geometry::IntegerSize2D& /*size*/ ) + { + return uno::Reference< rendering::XVolatileBitmap >(); + } + + void SpriteDeviceHelper::destroyBuffers() + { + // TODO(F3): implement XBufferStrategy interface. For now, we + // _always_ will have exactly one backbuffer + } + + bool SpriteDeviceHelper::showBuffer( bool, bool ) + { + SAL_WARN("canvas.directx", "Not supposed to be called, handled by SpriteCanvas"); + return false; + } + + bool SpriteDeviceHelper::switchBuffer( bool, bool ) + { + SAL_WARN("canvas.directx", "Not supposed to be called, handled by SpriteCanvas"); + return false; + } + + uno::Any SpriteDeviceHelper::isAccelerated() const + { + return css::uno::Any(true); + } + + void SpriteDeviceHelper::notifySizeUpdate( const awt::Rectangle& rBounds ) + { + // #i60490# ensure backbuffer has sensible minimal size + const sal_Int32 x(rBounds.X); + const sal_Int32 y(rBounds.Y); + const sal_Int32 w(std::max(sal_Int32(1),sal_Int32(rBounds.Width))); + const sal_Int32 h(std::max(sal_Int32(1),sal_Int32(rBounds.Height))); + + if( mpRenderModule ) + mpRenderModule->resize(::basegfx::B2IRange(x,y,x+w,y+h)); + + resizeBackBuffer(::basegfx::B2ISize(w,h)); + } + + void SpriteDeviceHelper::resizeBackBuffer( const ::basegfx::B2ISize& rNewSize ) + { + // disposed? + if(!mpBackBuffer) + return; + + mpBackBuffer->resize(rNewSize); + mpBackBuffer->clear(); + } + + HWND SpriteDeviceHelper::getHwnd() const + { + if( mpRenderModule ) + return mpRenderModule->getHWND(); + else + return nullptr; + } + + void SpriteDeviceHelper::dumpScreenContent() const + { + if( mpRenderModule ) + mpRenderModule->screenShot(); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_spritedevicehelper.hxx b/canvas/source/directx/dx_spritedevicehelper.hxx new file mode 100644 index 0000000000..ad74177706 --- /dev/null +++ b/canvas/source/directx/dx_spritedevicehelper.hxx @@ -0,0 +1,98 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/awt/Rectangle.hpp> +#include <com/sun/star/rendering/XGraphicDevice.hpp> +#include <com/sun/star/rendering/XBufferController.hpp> + +#include "dx_rendermodule.hxx" +#include "dx_surfacebitmap.hxx" +#include "dx_devicehelper.hxx" + +#include <rendering/isurfaceproxymanager.hxx> + + +namespace dxcanvas +{ + class SpriteCanvas; + + class SpriteDeviceHelper : public DeviceHelper + { + public: + SpriteDeviceHelper(); + + void init( vcl::Window& rWindow, + SpriteCanvas& rSpriteCanvas, + const css::awt::Rectangle& rRect, + bool bFullscreen ); + + /// Dispose all internal references + void disposing(); + + // partial override XWindowGraphicDevice + css::uno::Reference< css::rendering::XBitmap > createCompatibleBitmap( + const css::uno::Reference< css::rendering::XGraphicDevice >& rDevice, + const css::geometry::IntegerSize2D& size ); + css::uno::Reference< css::rendering::XVolatileBitmap > createVolatileBitmap( + const css::uno::Reference< css::rendering::XGraphicDevice >& rDevice, + const css::geometry::IntegerSize2D& size ); + css::uno::Reference< css::rendering::XBitmap > createCompatibleAlphaBitmap( + const css::uno::Reference< css::rendering::XGraphicDevice >& rDevice, + const css::geometry::IntegerSize2D& size ); + css::uno::Reference< css::rendering::XVolatileBitmap > createVolatileAlphaBitmap( + const css::uno::Reference< css::rendering::XGraphicDevice >& rDevice, + const css::geometry::IntegerSize2D& size ); + + void destroyBuffers( ); + bool showBuffer( bool bIsVisible, bool bUpdateAll ); + bool switchBuffer( bool bIsVisible, bool bUpdateAll ); + + const IDXRenderModuleSharedPtr& getRenderModule() const { return mpRenderModule; } + const DXSurfaceBitmapSharedPtr& getBackBuffer() const { return mpBackBuffer; } + const std::shared_ptr<canvas::ISurfaceProxyManager> &getSurfaceProxy() const { return mpSurfaceProxyManager; } + + css::uno::Any isAccelerated() const; + + void notifySizeUpdate( const css::awt::Rectangle& rBounds ); + + /** called when DumpScreenContent property is enabled on + XGraphicDevice, and writes out bitmaps of current screen. + */ + void dumpScreenContent() const; + + private: + void resizeBackBuffer( const ::basegfx::B2ISize& rNewSize ); + HWND getHwnd() const; + + /// Pointer to sprite canvas (owner of this helper), needed to create bitmaps + SpriteCanvas* mpSpriteCanvas; + + DXSurfaceBitmapSharedPtr mpBackBuffer; + + /// Instance passing out HW textures + std::shared_ptr<canvas::ISurfaceProxyManager> mpSurfaceProxyManager; + + /// Our encapsulation interface to DirectX + IDXRenderModuleSharedPtr mpRenderModule; + }; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_spritehelper.cxx b/canvas/source/directx/dx_spritehelper.cxx new file mode 100644 index 0000000000..3cb211886a --- /dev/null +++ b/canvas/source/directx/dx_spritehelper.cxx @@ -0,0 +1,199 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> +#include <sal/log.hxx> + +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/numeric/ftools.hxx> +#include <basegfx/point/b2dpoint.hxx> +#include <basegfx/polygon/b2dpolygoncutandtouch.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/polygon/b2dpolygontriangulator.hxx> +#include <basegfx/polygon/b2dpolypolygontools.hxx> +#include <basegfx/utils/canvastools.hxx> +#include <rtl/math.hxx> +#include <comphelper/diagnose_ex.hxx> + +#include <canvas/canvastools.hxx> + +#include "dx_canvascustomsprite.hxx" +#include "dx_impltools.hxx" +#include "dx_spritehelper.hxx" + +using namespace ::com::sun::star; + +namespace dxcanvas +{ + SpriteHelper::SpriteHelper() : + mpSpriteCanvas(), + mpBitmap(), + mbTextureDirty( true ), + mbShowSpriteBounds( false ) + { + } + + void SpriteHelper::init( const geometry::RealSize2D& rSpriteSize, + const SpriteCanvasRef& rSpriteCanvas, + const IDXRenderModuleSharedPtr& rRenderModule, + const DXSurfaceBitmapSharedPtr& rBitmap, + bool bShowSpriteBounds ) + { + ENSURE_OR_THROW( rSpriteCanvas && + rRenderModule && + rBitmap, + "SpriteHelper::init(): Invalid device, sprite canvas or surface" ); + + mpSpriteCanvas = rSpriteCanvas; + mpBitmap = rBitmap; + mbTextureDirty = true; + mbShowSpriteBounds = bShowSpriteBounds; + + // also init base class + CanvasCustomSpriteHelper::init( rSpriteSize, + rSpriteCanvas ); + } + + void SpriteHelper::disposing() + { + mpBitmap.reset(); + mpSpriteCanvas.clear(); + + // forward to parent + CanvasCustomSpriteHelper::disposing(); + } + + ::basegfx::B2DPolyPolygon SpriteHelper::polyPolygonFromXPolyPolygon2D( uno::Reference< rendering::XPolyPolygon2D >& xPoly ) const + { + return tools::polyPolygonFromXPolyPolygon2D( xPoly ); + } + + bool SpriteHelper::needRedraw() const + { + if( !mpBitmap || + !mpSpriteCanvas ) + { + return false; // we're disposed, no redraw necessary + } + + if( !isActive() || + ::basegfx::fTools::equalZero( getAlpha() ) ) + { + return false; // sprite is invisible + } + + return true; + } + + void SpriteHelper::redraw( bool& io_bSurfaceDirty ) const + { + if( !mpBitmap || + !mpSpriteCanvas ) + { + return; // we're disposed + } + + const ::basegfx::B2DPoint& rPos( getPosPixel() ); + const double fAlpha( getAlpha() ); + + if( isActive() && + !::basegfx::fTools::equalZero( fAlpha ) ) + { + + // TODO(Q2): For the time being, Device does not take a target + // surface, but always unconditionally renders to the + // background buffer. + + // log output pos in device pixel + SAL_INFO("canvas.directx", "SpriteHelper::redraw(): output pos is (" << + rPos.getX() << "," << rPos.getY() << ")" ); + + const ::basegfx::B2DVector& rSize( getSizePixel() ); + const ::basegfx::B2DHomMatrix& rTransform( getTransformation() ); + const uno::Reference< rendering::XPolyPolygon2D >& xClip( getClip() ); + + mbTextureDirty = false; + io_bSurfaceDirty = false; // state taken, and processed. + + ::basegfx::B2DPolyPolygon aClipPath; // empty for no clip + bool bIsClipRectangular( false ); // false, if no + // clip, or clip + // is complex + + // setup and apply clip (if any) + // ================================= + + if( xClip.is() ) + { + aClipPath = tools::polyPolygonFromXPolyPolygon2D( xClip ); + + const sal_Int32 nNumClipPolygons( aClipPath.count() ); + if( nNumClipPolygons ) + { + // TODO(P2): hold rectangle attribute directly + // at the XPolyPolygon2D + + // check whether the clip is rectangular + if( nNumClipPolygons == 1 ) + if( ::basegfx::utils::isRectangle( aClipPath.getB2DPolygon( 0 ) ) ) + bIsClipRectangular = true; + } + } + + const ::basegfx::B2DRectangle aSourceRect( 0.0, + 0.0, + rSize.getX(), + rSize.getY() ); + + // draw simple rectangular area if no clip is set. + if( !aClipPath.count() ) + { + mpBitmap->draw(fAlpha,rPos,rTransform); + } + else if( bIsClipRectangular ) + { + // apply a simple rect clip + // ======================== + + ::basegfx::B2DRectangle aClipBounds( + ::basegfx::utils::getRange( aClipPath ) ); + aClipBounds.intersect( aSourceRect ); + + mpBitmap->draw(fAlpha,rPos,aClipBounds,rTransform); + } + else + { + // apply clip the hard way + // ======================= + + mpBitmap->draw(fAlpha,rPos,aClipPath,rTransform); + } + + if( mbShowSpriteBounds ) + { + if( aClipPath.count() ) + { + // TODO(F2): Re-enable debug output + } + } + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_spritehelper.hxx b/canvas/source/directx/dx_spritehelper.hxx new file mode 100644 index 0000000000..86e6dbaf6c --- /dev/null +++ b/canvas/source/directx/dx_spritehelper.hxx @@ -0,0 +1,102 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/rendering/XCustomSprite.hpp> + +#include <base/canvascustomspritehelper.hxx> + +#include <basegfx/point/b2dpoint.hxx> +#include <basegfx/vector/b2isize.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> + +#include "dx_spritecanvas.hxx" +#include "dx_surfacebitmap.hxx" + +namespace dxcanvas +{ + /* Definition of SpriteHelper class */ + + /** Helper class for canvas sprites. + + This class implements all sprite-related functionality, like + that available on the XSprite interface. + */ + class SpriteHelper : public ::canvas::CanvasCustomSpriteHelper + { + public: + /** Create sprite helper + */ + SpriteHelper(); + + /** Late-init the sprite helper + + @param rSpriteSize + Size of the sprite + + @param rSpriteCanvas + Sprite canvas this sprite is part of. Object stores + ref-counted reference to it, thus, don't forget to pass on + disposing()! + + @param rRenderModule + rendermodule to use + + @param rSpriteSurface + The surface of the sprite (not the DX texture, but the + persistent target of content rendering) + + @param bShowSpriteBounds + When true, little debug bound rects for sprites are shown + */ + void init( const css::geometry::RealSize2D& rSpriteSize, + const SpriteCanvasRef& rSpriteCanvas, + const IDXRenderModuleSharedPtr& rRenderModule, + const DXSurfaceBitmapSharedPtr& rBitmap, + bool bShowSpriteBounds ); + + void disposing(); + + /** Repaint sprite content via hardware to associated sprite + canvas + + @param io_bSurfaceDirty + Input/output parameter, whether the sprite content is + dirty or not. If texture was updated, set to false + + */ + void redraw( bool& io_bSurfaceDirty ) const; + + private: + virtual ::basegfx::B2DPolyPolygon polyPolygonFromXPolyPolygon2D( + css::uno::Reference< css::rendering::XPolyPolygon2D >& xPoly ) const override; + + /// Returns true, if the sprite _really_ needs redraw + bool needRedraw() const; + + SpriteCanvasRef mpSpriteCanvas; + + DXSurfaceBitmapSharedPtr mpBitmap; + mutable bool mbTextureDirty; // when true, texture needs update + bool mbShowSpriteBounds; // when true, debug bound rect for sprites is shown + }; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_surfacebitmap.cxx b/canvas/source/directx/dx_surfacebitmap.cxx new file mode 100644 index 0000000000..5a3992eea2 --- /dev/null +++ b/canvas/source/directx/dx_surfacebitmap.cxx @@ -0,0 +1,654 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <memory> +#include <string.h> + +#include <com/sun/star/rendering/ColorComponentTag.hpp> + +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/range/b2irange.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <rendering/icolorbuffer.hxx> + +#include "dx_graphicsprovider.hxx" +#include "dx_impltools.hxx" +#include "dx_surfacebitmap.hxx" +#include "dx_surfacegraphics.hxx" + +using namespace ::com::sun::star; + +namespace dxcanvas +{ + namespace + { + + // DXColorBuffer + + + struct DXColorBuffer : public canvas::IColorBuffer + { + public: + DXColorBuffer( const sal::systools::COMReference<surface_type>& rSurface, + const ::basegfx::B2ISize& rSize ) + : maSize(rSize) + , maLockedRect{} + , mpSurface(rSurface) + { + } + + // implementation of the 'IColorBuffer' interface + public: + + virtual sal_uInt8* lock() const override; + virtual void unlock() const override; + virtual sal_uInt32 getWidth() const override; + virtual sal_uInt32 getHeight() const override; + virtual sal_uInt32 getStride() const override; + virtual Format getFormat() const override; + + private: + + ::basegfx::B2ISize maSize; + mutable D3DLOCKED_RECT maLockedRect; + sal::systools::COMReference<surface_type> mpSurface; + }; + + sal_uInt8* DXColorBuffer::lock() const + { + if(SUCCEEDED(mpSurface->LockRect(&maLockedRect,nullptr,D3DLOCK_NOSYSLOCK|D3DLOCK_READONLY))) + return static_cast<sal_uInt8 *>(maLockedRect.pBits); + return nullptr; + } + + void DXColorBuffer::unlock() const + { + mpSurface->UnlockRect(); + } + + sal_uInt32 DXColorBuffer::getWidth() const + { + return maSize.getWidth(); + } + + sal_uInt32 DXColorBuffer::getHeight() const + { + return maSize.getHeight(); + } + + sal_uInt32 DXColorBuffer::getStride() const + { + return maLockedRect.Pitch; + } + + canvas::IColorBuffer::Format DXColorBuffer::getFormat() const + { + return canvas::IColorBuffer::Format::X8R8G8B8; + } + + + // GDIColorBuffer + + + struct GDIColorBuffer : public canvas::IColorBuffer + { + public: + + GDIColorBuffer( const BitmapSharedPtr& rSurface, + const ::basegfx::B2ISize& rSize ) + : maSize(rSize) + , aBmpData{} + , mpGDIPlusBitmap(rSurface) + { + } + + // implementation of the 'IColorBuffer' interface + public: + + virtual sal_uInt8* lock() const override; + virtual void unlock() const override; + virtual sal_uInt32 getWidth() const override; + virtual sal_uInt32 getHeight() const override; + virtual sal_uInt32 getStride() const override; + virtual Format getFormat() const override; + + private: + + ::basegfx::B2ISize maSize; + mutable Gdiplus::BitmapData aBmpData; + BitmapSharedPtr mpGDIPlusBitmap; + }; + + sal_uInt8* GDIColorBuffer::lock() const + { + aBmpData.Width = maSize.getWidth(); + aBmpData.Height = maSize.getHeight(); + aBmpData.Stride = 4*aBmpData.Width; + aBmpData.PixelFormat = PixelFormat32bppARGB; + aBmpData.Scan0 = nullptr; + const Gdiplus::Rect aRect( 0,0,aBmpData.Width,aBmpData.Height ); + if( Gdiplus::Ok != mpGDIPlusBitmap->LockBits( &aRect, + Gdiplus::ImageLockModeRead, + PixelFormat32bppARGB, + &aBmpData ) ) + { + return nullptr; + } + + return static_cast<sal_uInt8*>(aBmpData.Scan0); + } + + void GDIColorBuffer::unlock() const + { + mpGDIPlusBitmap->UnlockBits( &aBmpData ); + } + + sal_uInt32 GDIColorBuffer::getWidth() const + { + return maSize.getWidth(); + } + + sal_uInt32 GDIColorBuffer::getHeight() const + { + return maSize.getHeight(); + } + + sal_uInt32 GDIColorBuffer::getStride() const + { + return aBmpData.Stride; + } + + canvas::IColorBuffer::Format GDIColorBuffer::getFormat() const + { + return canvas::IColorBuffer::Format::A8R8G8B8; + } + } + + + // DXSurfaceBitmap::DXSurfaceBitmap + + + DXSurfaceBitmap::DXSurfaceBitmap( const ::basegfx::B2ISize& rSize, + const std::shared_ptr<canvas::ISurfaceProxyManager>& rMgr, + const IDXRenderModuleSharedPtr& rRenderModule, + bool bWithAlpha ) : + mpGdiPlusUser( GDIPlusUser::createInstance() ), + maSize(rSize), + mpRenderModule(rRenderModule), + mpSurfaceManager(rMgr), + mpSurfaceProxy(), + mpSurface(), + mpGDIPlusBitmap(), + mpGraphics(), + mpColorBuffer(), + mbIsSurfaceDirty(true), + mbAlpha(bWithAlpha) + { + init(); + } + + + // DXSurfaceBitmap::getSize + + + ::basegfx::B2ISize DXSurfaceBitmap::getSize() const + { + return maSize; + } + + + // DXSurfaceBitmap::init + + + void DXSurfaceBitmap::init() + { + // create container for pixel data + if(mbAlpha) + { + mpGDIPlusBitmap = std::make_shared<Gdiplus::Bitmap>( + maSize.getWidth(), + maSize.getHeight(), + PixelFormat32bppARGB + ); + mpGraphics = tools::createGraphicsFromBitmap(mpGDIPlusBitmap); + + // create the colorbuffer object, which is basically a simple + // wrapper around the directx surface. the colorbuffer is the + // interface which is used by the surfaceproxy to support any + // kind of underlying structure for the pixel data container. + mpColorBuffer = std::make_shared<GDIColorBuffer>(mpGDIPlusBitmap, maSize); + } + else + { + mpSurface = mpRenderModule->createSystemMemorySurface(maSize); + + // create the colorbuffer object, which is basically a simple + // wrapper around the directx surface. the colorbuffer is the + // interface which is used by the surfaceproxy to support any + // kind of underlying structure for the pixel data container. + mpColorBuffer = std::make_shared<DXColorBuffer>(mpSurface, maSize); + } + + // create a (possibly hardware accelerated) mirror surface. + mpSurfaceProxy = mpSurfaceManager->createSurfaceProxy(mpColorBuffer); + } + + + // DXSurfaceBitmap::resize + + + bool DXSurfaceBitmap::resize(const ::basegfx::B2ISize& rSize) + { + if(maSize != rSize) + { + maSize = rSize; + init(); + } + + return true; + } + + + // DXSurfaceBitmap::clear + + + void DXSurfaceBitmap::clear() + { + GraphicsSharedPtr pGraphics(getGraphics()); + Gdiplus::Color transColor(255,0,0,0); + pGraphics->SetCompositingMode( Gdiplus::CompositingModeSourceCopy ); + pGraphics->Clear( transColor ); + } + + + // DXSurfaceBitmap::hasAlpha + + + bool DXSurfaceBitmap::hasAlpha() const + { + return mbAlpha; + } + + + // DXSurfaceBitmap::getGraphics + + + GraphicsSharedPtr DXSurfaceBitmap::getGraphics() + { + // since clients will most probably draw directly + // to the GDI+ bitmap, we need to mark it as dirty + // to ensure that the corresponding dxsurface will + // be updated. + mbIsSurfaceDirty = true; + + if(hasAlpha()) + return mpGraphics; + else + return createSurfaceGraphics(mpSurface); + } + + + // DXSurfaceBitmap::getBitmap + + + BitmapSharedPtr DXSurfaceBitmap::getBitmap() const + { + if(hasAlpha()) + return mpGDIPlusBitmap; + + BitmapSharedPtr pResult; + + D3DLOCKED_RECT aLockedRect; + if(SUCCEEDED(mpSurface->LockRect(&aLockedRect,nullptr,D3DLOCK_NOSYSLOCK|D3DLOCK_READONLY))) + { + // decide about the format we pass the gdi+, the directx surface is always + // 32bit, either with or without alpha component. + Gdiplus::PixelFormat nFormat = hasAlpha() ? PixelFormat32bppARGB : PixelFormat32bppRGB; + + // construct a gdi+ bitmap from the raw pixel data. + pResult = std::make_shared<Gdiplus::Bitmap>(maSize.getWidth(), maSize.getHeight(), + aLockedRect.Pitch, + nFormat, + static_cast<BYTE *>(aLockedRect.pBits) ); + + mpSurface->UnlockRect(); + } + + return pResult; + } + + + // DXSurfaceBitmap::draw + + + bool DXSurfaceBitmap::draw( double fAlpha, + const ::basegfx::B2DPoint& rPos, + const ::basegfx::B2DPolyPolygon& rClipPoly, + const ::basegfx::B2DHomMatrix& rTransform ) + { + if( mbIsSurfaceDirty ) + { + mpSurfaceProxy->setColorBufferDirty(); + mbIsSurfaceDirty = false; + } + + return mpSurfaceProxy->draw( fAlpha, rPos, rClipPoly, rTransform ); + } + + + // DXSurfaceBitmap::draw + + + bool DXSurfaceBitmap::draw( double fAlpha, + const ::basegfx::B2DPoint& rPos, + const ::basegfx::B2DRange& rArea, + const ::basegfx::B2DHomMatrix& rTransform ) + { + if( mbIsSurfaceDirty ) + { + mpSurfaceProxy->setColorBufferDirty(); + mbIsSurfaceDirty = false; + } + + return mpSurfaceProxy->draw( fAlpha, rPos, rArea, rTransform ); + } + + + // DXSurfaceBitmap::draw + + + bool DXSurfaceBitmap::draw( double fAlpha, + const ::basegfx::B2DPoint& rPos, + const ::basegfx::B2DHomMatrix& rTransform ) + { + if( mbIsSurfaceDirty ) + { + mpSurfaceProxy->setColorBufferDirty(); + mbIsSurfaceDirty = false; + } + + return mpSurfaceProxy->draw( fAlpha, rPos, rTransform ); + } + + + // DXSurfaceBitmap::draw + + + bool DXSurfaceBitmap::draw( const ::basegfx::B2IRange& rArea ) + { + if( mbIsSurfaceDirty ) + { + mpSurfaceProxy->setColorBufferDirty(); + mbIsSurfaceDirty = false; + } + + const double fAlpha(1.0); + const ::basegfx::B2DHomMatrix aTransform; + const ::basegfx::B2DRange aIEEEArea( rArea ); + return mpSurfaceProxy->draw(fAlpha, + ::basegfx::B2DPoint(), + aIEEEArea, + aTransform); + } + + + // DXSurfaceBitmap::getData + + + uno::Sequence< sal_Int8 > DXSurfaceBitmap::getData( rendering::IntegerBitmapLayout& rBitmapLayout, + const geometry::IntegerRectangle2D& rect ) + { + if(hasAlpha()) + { + uno::Sequence< sal_Int8 > aRes( (rect.X2-rect.X1)*(rect.Y2-rect.Y1)*4 ); // TODO(F1): Be format-agnostic here + + const Gdiplus::Rect aRect( tools::gdiPlusRectFromIntegerRectangle2D( rect ) ); + + Gdiplus::BitmapData aBmpData; + aBmpData.Width = rect.X2-rect.X1; + aBmpData.Height = rect.Y2-rect.Y1; + aBmpData.Stride = 4*aBmpData.Width; + aBmpData.PixelFormat = PixelFormat32bppARGB; + aBmpData.Scan0 = aRes.getArray(); + + // TODO(F1): Support more pixel formats natively + + // read data from bitmap + if( Gdiplus::Ok != mpGDIPlusBitmap->LockBits( &aRect, + Gdiplus::ImageLockModeRead | Gdiplus::ImageLockModeUserInputBuf, + PixelFormat32bppARGB, // TODO(F1): Adapt to + // Graphics native + // format/change + // getMemoryLayout + &aBmpData ) ) + { + // failed to lock, bail out + return uno::Sequence< sal_Int8 >(); + } + + mpGDIPlusBitmap->UnlockBits( &aBmpData ); + + return aRes; + } + else + { + sal_uInt32 nWidth = rect.X2-rect.X1; + sal_uInt32 nHeight = rect.Y2-rect.Y1; + + uno::Sequence< sal_Int8 > aRes(nWidth*nHeight*4); + + D3DLOCKED_RECT aLockedRect; + if(FAILED(mpSurface->LockRect(&aLockedRect,nullptr,D3DLOCK_NOSYSLOCK|D3DLOCK_READONLY))) + return uno::Sequence< sal_Int8 >(); + D3DSURFACE_DESC aDesc; + if(FAILED(mpSurface->GetDesc(&aDesc))) + return uno::Sequence< sal_Int8 >(); + + assert(aDesc.Format == D3DFMT_A8R8G8B8 || aDesc.Format == D3DFMT_X8R8G8B8); + + sal_uInt8 *pSrc = (static_cast<BYTE *>(aLockedRect.pBits)+(rect.Y1*aLockedRect.Pitch))+rect.X1; + sal_uInt8 *pDst = reinterpret_cast<sal_uInt8 *>(aRes.getArray()); + sal_uInt32 nSegmentSizeInBytes = nWidth*4; + for(sal_uInt32 y=0; y<nHeight; ++y) + { + memcpy(pDst,pSrc,nSegmentSizeInBytes); + pDst += nSegmentSizeInBytes; + pSrc += aLockedRect.Pitch; + } + + if(rBitmapLayout.ColorSpace->getComponentTags().getArray()[0] == rendering::ColorComponentTag::RGB_RED && + rBitmapLayout.ColorSpace->getComponentTags().getArray()[2] == rendering::ColorComponentTag::RGB_BLUE) + { + pDst = reinterpret_cast<sal_uInt8 *>(aRes.getArray()); + for(sal_uInt32 y=0; y<nHeight; ++y) + { + sal_uInt8* pPixel = pDst; + for(sal_uInt32 n = 0; n<nWidth; n++) + { + sal_uInt8 nB = pPixel[0]; + pPixel[0] = pPixel[2]; + pPixel[2] = nB; + pPixel += 4; + } + pDst += nSegmentSizeInBytes; + } + } + + mpSurface->UnlockRect(); + return aRes; + } + } + + + // DXSurfaceBitmap::setData + + + void DXSurfaceBitmap::setData( const uno::Sequence< sal_Int8 >& data, + const rendering::IntegerBitmapLayout& /*bitmapLayout*/, + const geometry::IntegerRectangle2D& rect ) + { + if(hasAlpha()) + { + const Gdiplus::Rect aRect( tools::gdiPlusRectFromIntegerRectangle2D( rect ) ); + + Gdiplus::BitmapData aBmpData; + aBmpData.Width = rect.X2-rect.X1; + aBmpData.Height = rect.Y2-rect.Y1; + aBmpData.Stride = 4*aBmpData.Width; + aBmpData.PixelFormat = PixelFormat32bppARGB; + aBmpData.Scan0 = const_cast<sal_Int8 *>(data.getConstArray()); + + // TODO(F1): Support more pixel formats natively + + if( Gdiplus::Ok != mpGDIPlusBitmap->LockBits( &aRect, + Gdiplus::ImageLockModeWrite | Gdiplus::ImageLockModeUserInputBuf, + PixelFormat32bppARGB, // TODO: Adapt to + // Graphics native + // format/change + // getMemoryLayout + &aBmpData ) ) + { + throw uno::RuntimeException("GDIPlus method call was unsuccessful, problem with locking bitmap aRect object"); + } + + // commit data to bitmap + mpGDIPlusBitmap->UnlockBits( &aBmpData ); + } + else + { + sal_uInt32 nWidth = rect.X2-rect.X1; + sal_uInt32 nHeight = rect.Y2-rect.Y1; + + // lock the directx surface to receive the pointer to the surface memory. + D3DLOCKED_RECT aLockedRect; + if(FAILED(mpSurface->LockRect(&aLockedRect,nullptr,D3DLOCK_NOSYSLOCK|D3DLOCK_READONLY))) + throw uno::RuntimeException("failed to lock directx surface to surface memory"); + + sal_uInt8 const *pSrc = reinterpret_cast<sal_uInt8 const *>(data.getConstArray()); + sal_uInt8 *pDst = (static_cast<BYTE *>(aLockedRect.pBits)+(rect.Y1*aLockedRect.Pitch))+rect.X1; + sal_uInt32 nSegmentSizeInBytes = nWidth<<4; + for(sal_uInt32 y=0; y<nHeight; ++y) + { + memcpy(pDst,pSrc,nSegmentSizeInBytes); + pSrc += nSegmentSizeInBytes; + pDst += aLockedRect.Pitch; + } + + mpSurface->UnlockRect(); + } + + mbIsSurfaceDirty = true; + } + + + // DXSurfaceBitmap::setPixel + + + void DXSurfaceBitmap::setPixel( const uno::Sequence< sal_Int8 >& color, + const rendering::IntegerBitmapLayout& /*bitmapLayout*/, + const geometry::IntegerPoint2D& pos ) + { + if(hasAlpha()) + { + const geometry::IntegerSize2D aSize( maSize.getWidth(), maSize.getHeight() ); + + ENSURE_ARG_OR_THROW( pos.X >= 0 && pos.X < aSize.Width, + "CanvasHelper::setPixel: X coordinate out of bounds" ); + ENSURE_ARG_OR_THROW( pos.Y >= 0 && pos.Y < aSize.Height, + "CanvasHelper::setPixel: Y coordinate out of bounds" ); + ENSURE_ARG_OR_THROW( color.getLength() > 3, + "CanvasHelper::setPixel: not enough color components" ); + + if( Gdiplus::Ok != mpGDIPlusBitmap->SetPixel( pos.X, pos.Y, + Gdiplus::Color( tools::sequenceToArgb( color )))) + { + throw uno::RuntimeException("Problem with setting the color of bitmap object"); + } + } + else + { + ENSURE_ARG_OR_THROW( pos.X >= 0 && pos.X < maSize.getWidth(), + "CanvasHelper::setPixel: X coordinate out of bounds" ); + ENSURE_ARG_OR_THROW( pos.Y >= 0 && pos.Y < maSize.getHeight(), + "CanvasHelper::setPixel: Y coordinate out of bounds" ); + ENSURE_ARG_OR_THROW( color.getLength() > 3, + "CanvasHelper::setPixel: not enough color components" ); + + Gdiplus::Color aColor(tools::sequenceToArgb(color)); + + // lock the directx surface to receive the pointer to the surface memory. + D3DLOCKED_RECT aLockedRect; + if(FAILED(mpSurface->LockRect(&aLockedRect,nullptr,D3DLOCK_NOSYSLOCK|D3DLOCK_READONLY))) + throw uno::RuntimeException("cannot lock the directx surface to surface memory"); + + sal_uInt32 *pDst = reinterpret_cast<sal_uInt32 *>((static_cast<BYTE *>(aLockedRect.pBits)+(pos.Y*aLockedRect.Pitch))+pos.X); + *pDst = aColor.GetValue(); + mpSurface->UnlockRect(); + } + + mbIsSurfaceDirty = true; + } + + + // DXSurfaceBitmap::getPixel + + + uno::Sequence< sal_Int8 > DXSurfaceBitmap::getPixel( rendering::IntegerBitmapLayout& /*bitmapLayout*/, + const geometry::IntegerPoint2D& pos ) + { + if(hasAlpha()) + { + const geometry::IntegerSize2D aSize(maSize.getWidth(), maSize.getHeight()); + + ENSURE_ARG_OR_THROW( pos.X >= 0 && pos.X < aSize.Width, + "CanvasHelper::getPixel: X coordinate out of bounds" ); + ENSURE_ARG_OR_THROW( pos.Y >= 0 && pos.Y < aSize.Height, + "CanvasHelper::getPixel: Y coordinate out of bounds" ); + + Gdiplus::Color aColor; + + if( Gdiplus::Ok != mpGDIPlusBitmap->GetPixel( pos.X, pos.Y, &aColor ) ) + return uno::Sequence< sal_Int8 >(); + + return tools::argbToIntSequence(aColor.GetValue()); + } + else + { + ENSURE_ARG_OR_THROW( pos.X >= 0 && pos.X < maSize.getWidth(), + "CanvasHelper::getPixel: X coordinate out of bounds" ); + ENSURE_ARG_OR_THROW( pos.Y >= 0 && pos.Y < maSize.getHeight(), + "CanvasHelper::getPixel: Y coordinate out of bounds" ); + + // lock the directx surface to receive the pointer to the surface memory. + D3DLOCKED_RECT aLockedRect; + if(FAILED(mpSurface->LockRect(&aLockedRect,nullptr,D3DLOCK_NOSYSLOCK|D3DLOCK_READONLY))) + throw uno::RuntimeException("failed to lock directX surface to surface memory"); + + sal_uInt32 *pDst = reinterpret_cast<sal_uInt32 *>((static_cast<BYTE *>(aLockedRect.pBits)+(pos.Y*aLockedRect.Pitch))+pos.X); + Gdiplus::Color aColor(*pDst); + mpSurface->UnlockRect(); + + return tools::argbToIntSequence(aColor.GetValue()); + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_surfacebitmap.hxx b/canvas/source/directx/dx_surfacebitmap.hxx new file mode 100644 index 0000000000..f39ebb5b97 --- /dev/null +++ b/canvas/source/directx/dx_surfacebitmap.hxx @@ -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 . + */ + +#pragma once + +#include <rendering/isurfaceproxy.hxx> +#include <rendering/isurfaceproxymanager.hxx> +#include "dx_ibitmap.hxx" +#include "dx_canvasfont.hxx" +#include "dx_gdiplususer.hxx" +#include "dx_rendermodule.hxx" + +namespace dxcanvas +{ + class DXSurfaceBitmap : public IBitmap + { + public: + DXSurfaceBitmap( const ::basegfx::B2ISize& rSize, + const std::shared_ptr<canvas::ISurfaceProxyManager>& rMgr, + const IDXRenderModuleSharedPtr& rRenderModule, + bool bWithAlpha ); + + bool resize(const ::basegfx::B2ISize& rSize); + void clear(); + + virtual GraphicsSharedPtr getGraphics() override; + + virtual BitmapSharedPtr getBitmap() const override; + virtual ::basegfx::B2ISize getSize() const override; + virtual bool hasAlpha() const override; + + sal::systools::COMReference<surface_type> getSurface() const { return mpSurface; } + + bool draw( double fAlpha, + const ::basegfx::B2DPoint& rPos, + const ::basegfx::B2DHomMatrix& rTransform ); + + bool draw( const ::basegfx::B2IRange& rArea ); + + bool draw( double fAlpha, + const ::basegfx::B2DPoint& rPos, + const ::basegfx::B2DRange& rArea, + const ::basegfx::B2DHomMatrix& rTransform ); + + bool draw( double fAlpha, + const ::basegfx::B2DPoint& rPos, + const ::basegfx::B2DPolyPolygon& rClipPoly, + const ::basegfx::B2DHomMatrix& rTransform ); + + virtual css::uno::Sequence< sal_Int8 > getData( + css::rendering::IntegerBitmapLayout& bitmapLayout, + const css::geometry::IntegerRectangle2D& rect ) override; + + virtual void setData( + const css::uno::Sequence< sal_Int8 >& data, + const css::rendering::IntegerBitmapLayout& bitmapLayout, + const css::geometry::IntegerRectangle2D& rect ) override; + + virtual void setPixel( + const css::uno::Sequence< sal_Int8 >& color, + const css::rendering::IntegerBitmapLayout& bitmapLayout, + const css::geometry::IntegerPoint2D& pos ) override; + + virtual css::uno::Sequence< sal_Int8 > getPixel( + css::rendering::IntegerBitmapLayout& bitmapLayout, + const css::geometry::IntegerPoint2D& pos ) override; + + private: + void init(); + + // Refcounted global GDI+ state container + GDIPlusUserSharedPtr mpGdiPlusUser; + + // size of this image in pixels [integral unit] + ::basegfx::B2ISize maSize; + + // pointer to the rendermodule, needed to create surfaces + // which are used as container for the actual pixel data. + // generally we could use any kind of storage, but GDI+ + // is not willing to render antialiased fonts unless we + // use this special kind of container, don't ask me why... + IDXRenderModuleSharedPtr mpRenderModule; + + // pointer to the surface manager, needed in case clients + // want to resize the bitmap. + std::shared_ptr<canvas::ISurfaceProxyManager> mpSurfaceManager; + + // access point to the surface proxy which handles + // the hardware-dependent rendering stuff. + std::shared_ptr< canvas::ISurfaceProxy > mpSurfaceProxy; + + // container for pixel data, we need to use a directx + // surface since GDI+ sucks... + sal::systools::COMReference<surface_type> mpSurface; + + // since GDI+ does not work correctly in case we + // run on a 16bit display [don't ask me why] we need + // to occasionally render to a native GDI+ bitmap. + BitmapSharedPtr mpGDIPlusBitmap; + // Graphics for the mpGDIPlusBitmap + GraphicsSharedPtr mpGraphics; + + // internal implementation of the iColorBuffer interface + std::shared_ptr<canvas::IColorBuffer> mpColorBuffer; + + // indicates whether the associated surface needs + // to refresh its contents or not. in other words, + // this flag is set iff both representations are + // out of sync. + mutable bool mbIsSurfaceDirty; + + // true if the bitmap contains an alpha channel + bool mbAlpha; + }; + + typedef std::shared_ptr< DXSurfaceBitmap > DXSurfaceBitmapSharedPtr; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_surfacegraphics.cxx b/canvas/source/directx/dx_surfacegraphics.cxx new file mode 100644 index 0000000000..075f2fc153 --- /dev/null +++ b/canvas/source/directx/dx_surfacegraphics.cxx @@ -0,0 +1,77 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include "dx_impltools.hxx" +#include "dx_surfacegraphics.hxx" + +using namespace ::com::sun::star; + +namespace dxcanvas +{ + namespace + { + struct GraphicsDeleter + { + sal::systools::COMReference<surface_type> mpSurface; + HDC maHDC; + + GraphicsDeleter(const sal::systools::COMReference<surface_type>& rSurface, HDC hdc) : + mpSurface(rSurface), + maHDC(hdc) + {} + + void operator()( Gdiplus::Graphics* pGraphics ) + { + if(!pGraphics) + return; + + pGraphics->Flush(Gdiplus::FlushIntentionSync); + delete pGraphics; + + if(mpSurface.is()) + mpSurface->ReleaseDC( maHDC ); + } + }; + } + + GraphicsSharedPtr createSurfaceGraphics(const sal::systools::COMReference<surface_type>& rSurface ) + { + GraphicsSharedPtr pRet; + HDC aHDC; + if( SUCCEEDED(rSurface->GetDC( &aHDC )) ) + { + Gdiplus::Graphics* pGraphics = Gdiplus::Graphics::FromHDC( aHDC ); + if(pGraphics) + { + tools::setupGraphics( *pGraphics ); + pRet.reset(pGraphics, + GraphicsDeleter(rSurface, aHDC)); + return pRet; + } + else + rSurface->ReleaseDC( aHDC ); + } + + throw uno::RuntimeException("could not get the DC to rSurface"); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_surfacegraphics.hxx b/canvas/source/directx/dx_surfacegraphics.hxx new file mode 100644 index 0000000000..4260383e2e --- /dev/null +++ b/canvas/source/directx/dx_surfacegraphics.hxx @@ -0,0 +1,36 @@ +/* -*- 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 . + */ + +#pragma once + +#include "dx_graphicsprovider.hxx" + +namespace dxcanvas +{ +/** Container providing a Gdiplus::Graphics for a Surface + + This wrapper class transparently handles allocation and + release of surface resources the RAII way (the + GraphicsSharedPtr returned has a deleter that does all the + necessary DX cleanup work). +*/ +GraphicsSharedPtr createSurfaceGraphics(const sal::systools::COMReference<surface_type>& rSurface); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_textlayout.cxx b/canvas/source/directx/dx_textlayout.cxx new file mode 100644 index 0000000000..e64dde5966 --- /dev/null +++ b/canvas/source/directx/dx_textlayout.cxx @@ -0,0 +1,253 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> +#include <sal/log.hxx> + +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/numeric/ftools.hxx> +#include <cppuhelper/supportsservice.hxx> + +#include "dx_bitmap.hxx" +#include "dx_spritecanvas.hxx" +#include "dx_textlayout.hxx" +#include "dx_textlayout_drawhelper.hxx" + +using namespace ::com::sun::star; + +namespace dxcanvas +{ + TextLayout::TextLayout( const rendering::StringContext& aText, + sal_Int8 nDirection, + sal_Int64 /*nRandomSeed*/, + const CanvasFont::ImplRef& rFont ) : + TextLayout_Base( m_aMutex ), + maText( aText ), + maLogicalAdvancements(), + mpFont( rFont ), + mnTextDirection( nDirection ) + { + } + + TextLayout::~TextLayout() + { + } + + void SAL_CALL TextLayout::disposing() + { + mpFont.clear(); + } + + // XTextLayout + uno::Sequence< uno::Reference< rendering::XPolyPolygon2D > > SAL_CALL TextLayout::queryTextShapes( ) + { + // TODO + return uno::Sequence< uno::Reference< rendering::XPolyPolygon2D > >(); + } + + uno::Sequence< geometry::RealRectangle2D > SAL_CALL TextLayout::queryInkMeasures( ) + { + // TODO + return uno::Sequence< geometry::RealRectangle2D >(); + } + + uno::Sequence< geometry::RealRectangle2D > SAL_CALL TextLayout::queryMeasures( ) + { + // TODO + return uno::Sequence< geometry::RealRectangle2D >(); + } + + uno::Sequence< double > SAL_CALL TextLayout::queryLogicalAdvancements( ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + + return maLogicalAdvancements; + } + + void SAL_CALL TextLayout::applyLogicalAdvancements( const uno::Sequence< double >& aAdvancements ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + + if( aAdvancements.getLength() != maText.Length ) + { + SAL_WARN("canvas.directx", "TextLayout::applyLogicalAdvancements(): mismatching number of advancements" ); + throw lang::IllegalArgumentException(); + } + + maLogicalAdvancements = aAdvancements; + } + + uno::Sequence< sal_Bool > SAL_CALL TextLayout::queryKashidaPositions( ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + + return maKashidaPositions; + } + + void SAL_CALL TextLayout::applyKashidaPositions( const uno::Sequence< sal_Bool >& aPositions ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + + if( aPositions.hasElements() && aPositions.getLength() != maText.Length ) + { + SAL_WARN("canvas.directx", "TextLayout::applyKashidaPositions(): mismatching number of positions" ); + throw lang::IllegalArgumentException("mismatching number of positions", getXWeak(), 1); + } + + maKashidaPositions = aPositions; + } + + geometry::RealRectangle2D SAL_CALL TextLayout::queryTextBounds( ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + + uno::Reference< rendering::XGraphicDevice > xGraphicDevice; + ::dxcanvas::TextLayoutDrawHelper aDrawHelper(xGraphicDevice); + + // render text + const geometry::RealRectangle2D aBounds( + aDrawHelper.queryTextBounds( + maText, + maLogicalAdvancements, + mpFont, + mpFont->getFontMatrix())); + + return aBounds; + } + + double SAL_CALL TextLayout::justify( double /*nSize*/ ) + { + // TODO + return 0.0; + } + + double SAL_CALL TextLayout::combinedJustify( const uno::Sequence< uno::Reference< rendering::XTextLayout > >& /*aNextLayouts*/, + double /*nSize*/ ) + { + // TODO + return 0.0; + } + + rendering::TextHit SAL_CALL TextLayout::getTextHit( const geometry::RealPoint2D& /*aHitPoint*/ ) + { + // TODO + return rendering::TextHit(); + } + + rendering::Caret SAL_CALL TextLayout::getCaret( sal_Int32 /*nInsertionIndex*/, + sal_Bool /*bExcludeLigatures*/ ) + { + // TODO + return rendering::Caret(); + } + + sal_Int32 SAL_CALL TextLayout::getNextInsertionIndex( sal_Int32 /*nStartIndex*/, + sal_Int32 /*nCaretAdvancement*/, + sal_Bool /*bExcludeLigatures*/ ) + { + // TODO + return 0; + } + + uno::Reference< rendering::XPolyPolygon2D > SAL_CALL TextLayout::queryVisualHighlighting( sal_Int32 /*nStartIndex*/, + sal_Int32 /*nEndIndex*/ ) + { + // TODO + return uno::Reference< rendering::XPolyPolygon2D >(); + } + + uno::Reference< rendering::XPolyPolygon2D > SAL_CALL TextLayout::queryLogicalHighlighting( sal_Int32 /*nStartIndex*/, + sal_Int32 /*nEndIndex*/ ) + { + // TODO + return uno::Reference< rendering::XPolyPolygon2D >(); + } + + double SAL_CALL TextLayout::getBaselineOffset( ) + { + // TODO + return 0.0; + } + + sal_Int8 SAL_CALL TextLayout::getMainTextDirection( ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + + return mnTextDirection; + } + + uno::Reference< rendering::XCanvasFont > SAL_CALL TextLayout::getFont( ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + + return mpFont; + } + + rendering::StringContext SAL_CALL TextLayout::getText( ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + + return maText; + } + + bool TextLayout::draw( const GraphicsSharedPtr& rGraphics, + const rendering::ViewState& rViewState, + const rendering::RenderState& rRenderState, + const ::basegfx::B2ISize& rOutputOffset, + const uno::Reference< rendering::XGraphicDevice >& xGraphicDevice, + bool bAlphaSurface ) const + { + ::osl::MutexGuard aGuard( m_aMutex ); + + ::dxcanvas::TextLayoutDrawHelper aDrawHelper(xGraphicDevice); + + // render text + aDrawHelper.drawText( + rGraphics, + rViewState, + rRenderState, + rOutputOffset, + maText, + maLogicalAdvancements, + maKashidaPositions, + mpFont, + mpFont->getFontMatrix(), + bAlphaSurface, + mnTextDirection != 0); + + return true; + } + + OUString SAL_CALL TextLayout::getImplementationName() + { + return "DXCanvas::TextLayout"; + } + + sal_Bool SAL_CALL TextLayout::supportsService( const OUString& ServiceName ) + { + return cppu::supportsService( this, ServiceName ); + } + + uno::Sequence< OUString > SAL_CALL TextLayout::getSupportedServiceNames() + { + return { "com.sun.star.rendering.TextLayout" }; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_textlayout.hxx b/canvas/source/directx/dx_textlayout.hxx new file mode 100644 index 0000000000..f0ae523e7b --- /dev/null +++ b/canvas/source/directx/dx_textlayout.hxx @@ -0,0 +1,107 @@ +/* -*- 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 . + */ + +#pragma once + +#include <cppuhelper/compbase.hxx> +#include <cppuhelper/basemutex.hxx> + +#include <com/sun/star/lang/XServiceInfo.hpp> + +#include <com/sun/star/rendering/XTextLayout.hpp> + +#include <basegfx/vector/b2isize.hxx> + +#include "dx_canvasfont.hxx" +#include "dx_ibitmap.hxx" +#include "dx_winstuff.hxx" +#include "dx_gdiplususer.hxx" + + +/* Definition of TextLayout class */ +namespace dxcanvas +{ + typedef ::cppu::WeakComponentImplHelper< css::rendering::XTextLayout, + css::lang::XServiceInfo > TextLayout_Base; + + class TextLayout : public ::cppu::BaseMutex, + public TextLayout_Base + { + public: + TextLayout( const css::rendering::StringContext& aText, + sal_Int8 nDirection, + sal_Int64 nRandomSeed, + const CanvasFont::ImplRef& rFont ); + /// make noncopyable + TextLayout(const TextLayout&) = delete; + const TextLayout& operator=(const TextLayout&) = delete; + + /// Dispose all internal references + virtual void SAL_CALL disposing() override; + + // XTextLayout + virtual css::uno::Sequence< css::uno::Reference< css::rendering::XPolyPolygon2D > > SAL_CALL queryTextShapes( ) override; + virtual css::uno::Sequence< css::geometry::RealRectangle2D > SAL_CALL queryInkMeasures( ) override; + virtual css::uno::Sequence< css::geometry::RealRectangle2D > SAL_CALL queryMeasures( ) override; + virtual css::uno::Sequence< double > SAL_CALL queryLogicalAdvancements( ) override; + virtual void SAL_CALL applyLogicalAdvancements( const css::uno::Sequence< double >& aAdvancements ) override; + virtual css::uno::Sequence< sal_Bool > SAL_CALL queryKashidaPositions( ) override; + virtual void SAL_CALL applyKashidaPositions( const css::uno::Sequence< sal_Bool >& aPositions ) override; + virtual css::geometry::RealRectangle2D SAL_CALL queryTextBounds( ) override; + virtual double SAL_CALL justify( double nSize ) override; + virtual double SAL_CALL combinedJustify( const css::uno::Sequence< css::uno::Reference< css::rendering::XTextLayout > >& aNextLayouts, double nSize ) override; + virtual css::rendering::TextHit SAL_CALL getTextHit( const css::geometry::RealPoint2D& aHitPoint ) override; + virtual css::rendering::Caret SAL_CALL getCaret( sal_Int32 nInsertionIndex, sal_Bool bExcludeLigatures ) override; + virtual sal_Int32 SAL_CALL getNextInsertionIndex( sal_Int32 nStartIndex, sal_Int32 nCaretAdvancement, sal_Bool bExcludeLigatures ) override; + virtual css::uno::Reference< css::rendering::XPolyPolygon2D > SAL_CALL queryVisualHighlighting( sal_Int32 nStartIndex, sal_Int32 nEndIndex ) override; + virtual css::uno::Reference< css::rendering::XPolyPolygon2D > SAL_CALL queryLogicalHighlighting( sal_Int32 nStartIndex, sal_Int32 nEndIndex ) override; + virtual double SAL_CALL getBaselineOffset( ) override; + virtual sal_Int8 SAL_CALL getMainTextDirection( ) override; + virtual css::uno::Reference< css::rendering::XCanvasFont > SAL_CALL getFont( ) override; + virtual css::rendering::StringContext SAL_CALL getText( ) override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override; + + bool draw( const GraphicsSharedPtr& rGraphics, + const css::rendering::ViewState& rViewState, + const css::rendering::RenderState& rRenderState, + const ::basegfx::B2ISize& rOutputOffset, + const css::uno::Reference< + css::rendering::XGraphicDevice >& xGraphicDevice, + bool bAlphaSurface ) const; + + protected: + ~TextLayout() override; // we're a ref-counted UNO class. _We_ destroy ourselves. + + private: + // NOTE: no need for GDIPlusUserSharedPtr, mpFont implicitly has one already + + css::rendering::StringContext maText; + css::uno::Sequence< double > maLogicalAdvancements; + css::uno::Sequence< sal_Bool > maKashidaPositions; + CanvasFont::ImplRef mpFont; + sal_Int8 mnTextDirection; + }; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_textlayout_drawhelper.cxx b/canvas/source/directx/dx_textlayout_drawhelper.cxx new file mode 100644 index 0000000000..3a01af375a --- /dev/null +++ b/canvas/source/directx/dx_textlayout_drawhelper.cxx @@ -0,0 +1,317 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <memory> + +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/polygon/b2dpolypolygon.hxx> +#include <basegfx/utils/canvastools.hxx> +#include <com/sun/star/rendering/FontRequest.hpp> +#include <com/sun/star/rendering/PanoseProportion.hpp> +#include <com/sun/star/rendering/XCanvasFont.hpp> +#include <comphelper/scopeguard.hxx> +#include <i18nlangtag/languagetag.hxx> +#include <rtl/math.hxx> +#include <tools/color.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <tools/poly.hxx> +#include <vcl/canvastools.hxx> +#include <vcl/kernarray.hxx> +#include <vcl/metric.hxx> +#include <vcl/sysdata.hxx> +#include <vcl/virdev.hxx> + +#include <canvas/canvastools.hxx> + +#include "dx_bitmap.hxx" +#include "dx_canvasfont.hxx" +#include "dx_impltools.hxx" +#include "dx_textlayout_drawhelper.hxx" + +using namespace ::com::sun::star; + + +namespace dxcanvas +{ + TextLayoutDrawHelper::TextLayoutDrawHelper( + const uno::Reference< rendering::XGraphicDevice >& xGraphicDevice ) : + mxGraphicDevice(xGraphicDevice) + { + } + + TextLayoutDrawHelper::~TextLayoutDrawHelper() + { + } + + void TextLayoutDrawHelper::drawText( + const std::shared_ptr<Gdiplus::Graphics>& rGraphics, + const css::rendering::ViewState& rViewState, + const css::rendering::RenderState& rRenderState, + const ::basegfx::B2ISize& rOutputOffset, + const css::rendering::StringContext& rText, + const css::uno::Sequence< double >& rLogicalAdvancements, + const css::uno::Sequence< sal_Bool >& rKashidaPositions, + const css::uno::Reference< + css::rendering::XCanvasFont >& rCanvasFont, + const css::geometry::Matrix2D& rFontMatrix, + bool bAlphaSurface, + bool bIsRTL) + { + HDC hdc = rGraphics->GetHDC(); + + // issue a ReleaseHDC() when leaving the scope + const ::comphelper::ScopeGuard aGuard( + [&rGraphics, &hdc]() mutable { rGraphics->ReleaseHDC(hdc); } ); + + SystemGraphicsData aSystemGraphicsData; + aSystemGraphicsData.nSize = sizeof(SystemGraphicsData); + aSystemGraphicsData.hDC = reinterpret_cast< ::HDC >(hdc); + ScopedVclPtrInstance<VirtualDevice> xVirtualDevice(aSystemGraphicsData, Size(1, 1), DeviceFormat::WITHOUT_ALPHA); + + // disable font antialiasing - GDI does not handle alpha + // surfaces properly. + if( bAlphaSurface ) + xVirtualDevice->SetAntialiasing(AntialiasingFlags::DisableText); + + if(rText.Length) + { + bool test = mxGraphicDevice.is(); + ENSURE_OR_THROW( test, + "TextLayoutDrawHelper::drawText(): Invalid GraphicDevice" ); + + // set text color. Make sure to remove transparence part first. + Color aColor( COL_WHITE ); + + if( rRenderState.DeviceColor.getLength() > 2 ) + aColor = vcl::unotools::doubleSequenceToColor( + rRenderState.DeviceColor, + mxGraphicDevice->getDeviceColorSpace()); + aColor.SetAlpha(255); + xVirtualDevice->SetTextColor(aColor); + + // create the font + const css::rendering::FontRequest& rFontRequest = rCanvasFont->getFontRequest(); + vcl::Font aFont( + rFontRequest.FontDescription.FamilyName, + rFontRequest.FontDescription.StyleName, + Size( 0, ::basegfx::fround(rFontRequest.CellSize))); + + aFont.SetAlignment( ALIGN_BASELINE ); + aFont.SetCharSet( (rFontRequest.FontDescription.IsSymbolFont==css::util::TriState_YES) ? RTL_TEXTENCODING_SYMBOL : RTL_TEXTENCODING_UNICODE ); + aFont.SetVertical( rFontRequest.FontDescription.IsVertical==css::util::TriState_YES ); + aFont.SetWeight( static_cast<FontWeight>(rFontRequest.FontDescription.FontDescription.Weight) ); + aFont.SetItalic( (rFontRequest.FontDescription.FontDescription.Letterform<=8) ? ITALIC_NONE : ITALIC_NORMAL ); + aFont.SetPitch( + rFontRequest.FontDescription.FontDescription.Proportion == rendering::PanoseProportion::MONO_SPACED + ? PITCH_FIXED : PITCH_VARIABLE); + + aFont.SetLanguage(LanguageTag::convertToLanguageType(rFontRequest.Locale)); + + // setup font color + aFont.SetColor( aColor ); + aFont.SetFillColor( aColor ); + + CanvasFont::ImplRef pFont(tools::canvasFontFromXFont(rCanvasFont)); + if (pFont.is() && pFont->getEmphasisMark()) + aFont.SetEmphasisMark(FontEmphasisMark(pFont->getEmphasisMark())); + + // adjust to stretched font + if(!::rtl::math::approxEqual(rFontMatrix.m00, rFontMatrix.m11)) + { + const Size aSize = xVirtualDevice->GetFontMetric( aFont ).GetFontSize(); + const double fDividend( rFontMatrix.m10 + rFontMatrix.m11 ); + double fStretch = rFontMatrix.m00 + rFontMatrix.m01; + + if( !::basegfx::fTools::equalZero( fDividend) ) + fStretch /= fDividend; + + const sal_Int32 nNewWidth = ::basegfx::fround( aSize.Width() * fStretch ); + + aFont.SetAverageFontWidth( nNewWidth ); + } + + // set font + xVirtualDevice->SetFont(aFont); + + // create world transformation matrix + ::basegfx::B2DHomMatrix aWorldTransform; + ::canvas::tools::mergeViewAndRenderTransform(aWorldTransform, rViewState, rRenderState); + + if(!rOutputOffset.equalZero()) + { + aWorldTransform.translate(rOutputOffset.getWidth(), rOutputOffset.getHeight()); + } + + // set ViewState clipping + if(rViewState.Clip.is()) + { + ::basegfx::B2DPolyPolygon aClipPoly(dxcanvas::tools::polyPolygonFromXPolyPolygon2D(rViewState.Clip)); + ::basegfx::B2DHomMatrix aMatrix; + ::basegfx::unotools::homMatrixFromAffineMatrix(aMatrix, rViewState.AffineTransform ); + + if(!rOutputOffset.equalZero()) + { + aMatrix.translate(rOutputOffset.getWidth(), rOutputOffset.getHeight()); + } + + aClipPoly.transform(aMatrix); + const vcl::Region& rClipRegion = vcl::Region(::tools::PolyPolygon(aClipPoly)); + xVirtualDevice->IntersectClipRegion(rClipRegion); + } + + if(rRenderState.Clip.is()) + { + ::basegfx::B2DPolyPolygon aClipPoly(dxcanvas::tools::polyPolygonFromXPolyPolygon2D(rRenderState.Clip)); + aClipPoly.transform(aWorldTransform); + const vcl::Region& rClipRegion = vcl::Region(::tools::PolyPolygon(aClipPoly)); + xVirtualDevice->IntersectClipRegion(rClipRegion); + } + + // set world transform + XFORM aXForm; + aXForm.eM11 = static_cast<FLOAT>(aWorldTransform.get(0, 0)); + aXForm.eM12 = static_cast<FLOAT>(aWorldTransform.get(1, 0)); + aXForm.eM21 = static_cast<FLOAT>(aWorldTransform.get(0, 1)); + aXForm.eM22 = static_cast<FLOAT>(aWorldTransform.get(1, 1)); + aXForm.eDx = static_cast<FLOAT>(aWorldTransform.get(0, 2)); + aXForm.eDy = static_cast<FLOAT>(aWorldTransform.get(1, 2)); + + // TODO(F3): This is NOT supported on 95/98/ME! + SetGraphicsMode(hdc, GM_ADVANCED); + SetTextAlign(hdc, TA_BASELINE); + SetWorldTransform(hdc, &aXForm); + + // use an empty StartPosition for text rendering + const Point aEmptyPoint(0, 0); + + // create the String + const OUString aText(rText.Text); + + if( rLogicalAdvancements.getLength() ) + { + // create the DXArray + const sal_Int32 nLen( rLogicalAdvancements.getLength() ); + KernArray DXArray; + DXArray.reserve(nLen); + for( sal_Int32 i=0; i<nLen; ++i ) + DXArray.push_back(basegfx::fround(rLogicalAdvancements[i])); + + std::span<const sal_Bool> aKashidaArray(rKashidaPositions.getConstArray(), rKashidaPositions.getLength()); + + // draw the String + xVirtualDevice->DrawTextArray( aEmptyPoint, + aText, + DXArray, + aKashidaArray, + rText.StartPosition, + rText.Length, + bIsRTL ? SalLayoutFlags::BiDiRtl : SalLayoutFlags::NONE); + } + else + { + // draw the String + xVirtualDevice->DrawText( aEmptyPoint, + aText, + rText.StartPosition, + rText.Length ); + } + } + } + + geometry::RealRectangle2D TextLayoutDrawHelper::queryTextBounds( const rendering::StringContext& rText, + const uno::Sequence< double >& rLogicalAdvancements, + const uno::Reference< rendering::XCanvasFont >& rCanvasFont, + const geometry::Matrix2D& rFontMatrix ) + { + if(!(rText.Length)) + return geometry::RealRectangle2D(); + + // TODO(F1): Fetching default screen DC here, will yield wrong + // metrics when e.g. formatting for a printer! + SystemGraphicsData aSystemGraphicsData; + aSystemGraphicsData.nSize = sizeof(SystemGraphicsData); + aSystemGraphicsData.hDC = reinterpret_cast< ::HDC >(GetDC( nullptr )); + ScopedVclPtrInstance<VirtualDevice> xVirtualDevice(aSystemGraphicsData, Size(1, 1), DeviceFormat::WITHOUT_ALPHA); + + // create the font + const css::rendering::FontRequest& rFontRequest = rCanvasFont->getFontRequest(); + vcl::Font aFont( + rFontRequest.FontDescription.FamilyName, + rFontRequest.FontDescription.StyleName, + Size( 0, ::basegfx::fround(rFontRequest.CellSize))); + + aFont.SetAlignment( ALIGN_BASELINE ); + aFont.SetCharSet( (rFontRequest.FontDescription.IsSymbolFont==css::util::TriState_YES) ? RTL_TEXTENCODING_SYMBOL : RTL_TEXTENCODING_UNICODE ); + aFont.SetVertical( rFontRequest.FontDescription.IsVertical==css::util::TriState_YES ); + aFont.SetWeight( static_cast<FontWeight>(rFontRequest.FontDescription.FontDescription.Weight) ); + aFont.SetItalic( (rFontRequest.FontDescription.FontDescription.Letterform<=8) ? ITALIC_NONE : ITALIC_NORMAL ); + aFont.SetPitch( + rFontRequest.FontDescription.FontDescription.Proportion == rendering::PanoseProportion::MONO_SPACED + ? PITCH_FIXED : PITCH_VARIABLE); + + // adjust to stretched font + if(!::rtl::math::approxEqual(rFontMatrix.m00, rFontMatrix.m11)) + { + const Size aSize = xVirtualDevice->GetFontMetric( aFont ).GetFontSize(); + const double fDividend( rFontMatrix.m10 + rFontMatrix.m11 ); + double fStretch = rFontMatrix.m00 + rFontMatrix.m01; + + if( !::basegfx::fTools::equalZero( fDividend) ) + fStretch /= fDividend; + + const sal_Int32 nNewWidth = ::basegfx::fround( aSize.Width() * fStretch ); + + aFont.SetAverageFontWidth( nNewWidth ); + } + + CanvasFont::ImplRef pFont(tools::canvasFontFromXFont(rCanvasFont)); + if (pFont.is() && pFont->getEmphasisMark()) + aFont.SetEmphasisMark(FontEmphasisMark(pFont->getEmphasisMark())); + + // set font + xVirtualDevice->SetFont(aFont); + + // need metrics for Y offset, the XCanvas always renders + // relative to baseline + const ::FontMetric& aMetric( xVirtualDevice->GetFontMetric() ); + + const sal_Int32 nAboveBaseline( -aMetric.GetInternalLeading() - aMetric.GetAscent() ); + const sal_Int32 nBelowBaseline( aMetric.GetDescent() ); + + if( rLogicalAdvancements.getLength() ) + { + return geometry::RealRectangle2D( 0, nAboveBaseline, + rLogicalAdvancements[ rLogicalAdvancements.getLength()-1 ], + nBelowBaseline ); + } + else + { + return geometry::RealRectangle2D( 0, nAboveBaseline, + xVirtualDevice->GetTextWidth( + rText.Text, + ::canvas::tools::numeric_cast<sal_uInt16>(rText.StartPosition), + ::canvas::tools::numeric_cast<sal_uInt16>(rText.Length) ), + nBelowBaseline ); + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_textlayout_drawhelper.hxx b/canvas/source/directx/dx_textlayout_drawhelper.hxx new file mode 100644 index 0000000000..8e9383a8ae --- /dev/null +++ b/canvas/source/directx/dx_textlayout_drawhelper.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 . + */ + +#pragma once + +#include <memory> + +#include <com/sun/star/uno/Sequence.hxx> +#include <com/sun/star/rendering/StringContext.hpp> +#include <com/sun/star/rendering/XCanvasFont.hpp> +#include <com/sun/star/geometry/Matrix2D.hpp> +#include <com/sun/star/rendering/XGraphicDevice.hpp> +#include <com/sun/star/rendering/ViewState.hpp> +#include <com/sun/star/rendering/RenderState.hpp> + +#include <basegfx/vector/b2isize.hxx> + +namespace com::sun::star::rendering { class XCanvasFont; } + +namespace Gdiplus { class Graphics; } + +namespace dxcanvas +{ + struct Bitmap; + class TextLayoutDrawHelper + { + public: + explicit TextLayoutDrawHelper( + const css::uno::Reference< css::rendering::XGraphicDevice >& xGraphicDevice); + ~TextLayoutDrawHelper(); + + // draw text + void drawText( const std::shared_ptr<Gdiplus::Graphics>& rGraphics, + const css::rendering::ViewState& rViewState, + const css::rendering::RenderState& rRenderState, + const ::basegfx::B2ISize& rOutputOffset, + const css::rendering::StringContext& rText, + const css::uno::Sequence< double >& rLogicalAdvancements, + const css::uno::Sequence< sal_Bool >& rKashidaPositions, + const css::uno::Reference< + css::rendering::XCanvasFont >& rCanvasFont, + const css::geometry::Matrix2D& rFontMatrix, + bool bAlphaSurface, + bool bIsRTL); + + css::geometry::RealRectangle2D queryTextBounds( + const css::rendering::StringContext& rText, + const css::uno::Sequence< double >& rLogicalAdvancements, + const css::uno::Reference< + css::rendering::XCanvasFont >& rCanvasFont, + const css::geometry::Matrix2D& rFontMatrix ); + +#ifdef DBG_UTIL + void test(); +#endif + + protected: + css::uno::Reference< css::rendering::XGraphicDevice > mxGraphicDevice; + }; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_vcltools.cxx b/canvas/source/directx/dx_vcltools.cxx new file mode 100644 index 0000000000..31b05be58e --- /dev/null +++ b/canvas/source/directx/dx_vcltools.cxx @@ -0,0 +1,307 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <basegfx/numeric/ftools.hxx> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/rendering/XIntegerBitmap.hpp> +#include <comphelper/diagnose_ex.hxx> +#include <vcl/bitmap.hxx> +#include <vcl/bitmapex.hxx> +#include <vcl/BitmapReadAccess.hxx> +#include <vcl/canvastools.hxx> + +#include "dx_impltools.hxx" +#include "dx_vcltools.hxx" + +using namespace ::com::sun::star; + +namespace dxcanvas::tools +{ + namespace + { + /// Calc number of colors in given BitmapInfoHeader + sal_Int32 calcDIBColorCount( const BITMAPINFOHEADER& rBIH ) + { + if( rBIH.biSize != sizeof( BITMAPCOREHEADER ) ) + { + if( rBIH.biBitCount <= 8 ) + { + if( rBIH.biClrUsed ) + return rBIH.biClrUsed; + else + return 1 << rBIH.biBitCount; + } + } + else + { + BITMAPCOREHEADER const * pCoreHeader = reinterpret_cast<BITMAPCOREHEADER const *>(&rBIH); + + if( pCoreHeader->bcBitCount <= 8 ) + return 1 << pCoreHeader->bcBitCount; + } + + return 0; // nothing known + } + + /// Draw DI bits to given Graphics + bool drawDIBits( const std::shared_ptr< Gdiplus::Graphics >& rGraphics, + const void* hDIB ) + { + bool bRet( false ); + + const BITMAPINFO* pBI = static_cast<BITMAPINFO*>(GlobalLock( const_cast<void *>(hDIB) )); + + if( pBI ) + { + const BYTE* pBits = reinterpret_cast<BYTE const *>(pBI) + pBI->bmiHeader.biSize + + calcDIBColorCount( pBI->bmiHeader ) * sizeof( RGBQUAD ); + + // forward to outsourced GDI+ rendering method + // (header clashes) + bRet = tools::drawDIBits( rGraphics, *pBI, pBits ); + + GlobalUnlock( const_cast<void *>(hDIB) ); + } + + return bRet; + } + + /** Draw VCL bitmap to given Graphics + + @param rBmp + Reference to bitmap. Might get modified, in such a way + that it will hold a DIB after a successful function call. + */ + bool drawVCLBitmap( const std::shared_ptr< Gdiplus::Graphics >& rGraphics, + ::Bitmap& rBmp ) + { + BitmapSystemData aBmpSysData; + + if( !rBmp.GetSystemData( aBmpSysData ) || + !aBmpSysData.pDIB ) + { + // first of all, ensure that Bitmap contains a DIB, by + // acquiring a read access + BitmapScopedReadAccess pReadAcc(rBmp); + + // TODO(P2): Acquiring a read access can actually + // force a read from VRAM, thus, avoiding this + // step somehow will increase performance + // here. + if( pReadAcc ) + { + // try again: now, WinSalBitmap must have + // generated a DIB + if( rBmp.GetSystemData( aBmpSysData ) && + aBmpSysData.pDIB ) + { + return drawDIBits( rGraphics, + aBmpSysData.pDIB ); + } + } + } + else + { + return drawDIBits( rGraphics, + aBmpSysData.pDIB ); + } + + // failed to generate DIBits from vcl bitmap + return false; + } + + /** Create a chunk of raw RGBA data GDI+ Bitmap from VCL BitmapEx + */ + RawRGBABitmap bitmapFromVCLBitmapEx( const ::BitmapEx& rBmpEx ) + { + // TODO(P2): Avoid temporary bitmap generation, maybe + // even ensure that created DIBs are copied back to + // BmpEx (currently, every AcquireReadAccess() will + // make the local bitmap copy unique, effectively + // duplicating the memory used) + + ENSURE_OR_THROW( rBmpEx.IsAlpha(), + "::dxcanvas::tools::bitmapFromVCLBitmapEx(): " + "BmpEx has no alpha" ); + + // convert transparent bitmap to 32bit RGBA + // ======================================== + + const ::Size aBmpSize( rBmpEx.GetSizePixel() ); + + RawRGBABitmap aBmpData; + aBmpData.mnWidth = aBmpSize.Width(); + aBmpData.mnHeight = aBmpSize.Height(); + aBmpData.maBitmapData.resize(4*aBmpData.mnWidth*aBmpData.mnHeight); + + Bitmap aBitmap( rBmpEx.GetBitmap() ); + + BitmapScopedReadAccess pReadAccess( aBitmap ); + + const sal_Int32 nWidth( aBmpSize.Width() ); + const sal_Int32 nHeight( aBmpSize.Height() ); + + ENSURE_OR_THROW( pReadAccess.get() != nullptr, + "::dxcanvas::tools::bitmapFromVCLBitmapEx(): " + "Unable to acquire read access to bitmap" ); + + Bitmap aAlpha( rBmpEx.GetAlphaMask().GetBitmap() ); + + BitmapScopedReadAccess pAlphaReadAccess( aAlpha ); + + // By convention, the access buffer always has + // one of the following formats: + + // ScanlineFormat::N1BitMsbPal + // ScanlineFormat::N8BitPal + // ScanlineFormat::N24BitTcBgr + // ScanlineFormat::N32BitTcMask + + // and is always ScanlineFormat::BottomUp + + // This is the way + // WinSalBitmap::AcquireBuffer() sets up the + // buffer + + ENSURE_OR_THROW( pAlphaReadAccess.get() != nullptr, + "::dxcanvas::tools::bitmapFromVCLBitmapEx(): " + "Unable to acquire read access to alpha" ); + + ENSURE_OR_THROW( pAlphaReadAccess->GetScanlineFormat() == ScanlineFormat::N8BitPal, + "::dxcanvas::tools::bitmapFromVCLBitmapEx(): " + "Unsupported alpha scanline format" ); + + BitmapColor aCol; + sal_uInt8* pCurrOutput(aBmpData.maBitmapData.data()); + int x, y; + + for( y=0; y<nHeight; ++y ) + { + switch( pReadAccess->GetScanlineFormat() ) + { + case ScanlineFormat::N8BitPal: + { + Scanline pScan = pReadAccess->GetScanline( y ); + Scanline pAScan = pAlphaReadAccess->GetScanline( y ); + + for( x=0; x<nWidth; ++x ) + { + aCol = pReadAccess->GetPaletteColor( *pScan++ ); + + *pCurrOutput++ = aCol.GetBlue(); + *pCurrOutput++ = aCol.GetGreen(); + *pCurrOutput++ = aCol.GetRed(); + *pCurrOutput++ = static_cast<BYTE>(*pAScan++); + } + } + break; + + case ScanlineFormat::N24BitTcBgr: + { + Scanline pScan = pReadAccess->GetScanline( y ); + Scanline pAScan = pAlphaReadAccess->GetScanline( y ); + + for( x=0; x<nWidth; ++x ) + { + // store as RGBA + *pCurrOutput++ = *pScan++; + *pCurrOutput++ = *pScan++; + *pCurrOutput++ = *pScan++; + *pCurrOutput++ = static_cast<BYTE>(*pAScan++); + } + } + break; + + // TODO(P2): Might be advantageous + // to hand-formulate the following + // formats, too. + case ScanlineFormat::N1BitMsbPal: + case ScanlineFormat::N32BitTcMask: + { + Scanline pAScan = pAlphaReadAccess->GetScanline( y ); + + // using fallback for those + // seldom formats + for( x=0; x<nWidth; ++x ) + { + // yes. x and y are swapped on Get/SetPixel + aCol = pReadAccess->GetColor(y,x); + + *pCurrOutput++ = aCol.GetBlue(); + *pCurrOutput++ = aCol.GetGreen(); + *pCurrOutput++ = aCol.GetRed(); + *pCurrOutput++ = static_cast<BYTE>(*pAScan++); + } + } + break; + + case ScanlineFormat::N24BitTcRgb: + case ScanlineFormat::N32BitTcAbgr: + case ScanlineFormat::N32BitTcArgb: + case ScanlineFormat::N32BitTcBgra: + case ScanlineFormat::N32BitTcRgba: + default: + ENSURE_OR_THROW( false, + "::dxcanvas::tools::bitmapFromVCLBitmapEx(): " + "Unexpected scanline format - has " + "WinSalBitmap::AcquireBuffer() changed?" ); + } + } + + return aBmpData; + } + + bool drawVCLBitmapEx( const std::shared_ptr< Gdiplus::Graphics >& rGraphics, + const ::BitmapEx& rBmpEx ) + { + if( !rBmpEx.IsAlpha() ) + { + Bitmap aBmp( rBmpEx.GetBitmap() ); + return drawVCLBitmap( rGraphics, aBmp ); + } + else + { + return drawRGBABits( rGraphics, + bitmapFromVCLBitmapEx( rBmpEx ) ); + } + } + } + + bool drawVCLBitmapFromXBitmap( const std::shared_ptr< Gdiplus::Graphics >& rGraphics, + const uno::Reference< rendering::XBitmap >& xBitmap ) + { + // TODO(F2): add support for floating point bitmap formats + uno::Reference< rendering::XIntegerReadOnlyBitmap > xIntBmp( + xBitmap, uno::UNO_QUERY ); + + if( !xIntBmp.is() ) + return false; + + ::BitmapEx aBmpEx = vcl::unotools::bitmapExFromXBitmap( xIntBmp ); + if( aBmpEx.IsEmpty() ) + return false; + + return drawVCLBitmapEx( rGraphics, aBmpEx ); + } +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_vcltools.hxx b/canvas/source/directx/dx_vcltools.hxx new file mode 100644 index 0000000000..433afa618e --- /dev/null +++ b/canvas/source/directx/dx_vcltools.hxx @@ -0,0 +1,47 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/rendering/XBitmap.hpp> +#include <memory> +#include <vector> + +namespace Gdiplus { class Graphics; } + +namespace dxcanvas::tools +{ + /** Raw RGBA bitmap data, + contiguous in memory + */ + struct RawRGBABitmap + { + sal_Int32 mnWidth; + sal_Int32 mnHeight; + std::vector<sal_uInt8> maBitmapData; + }; + + bool drawVCLBitmapFromXBitmap( const std::shared_ptr< Gdiplus::Graphics >& rGraphics, + const css::uno::Reference< + css::rendering::XBitmap >& xBitmap ); +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_winstuff.hxx b/canvas/source/directx/dx_winstuff.hxx new file mode 100644 index 0000000000..4cd0007fa9 --- /dev/null +++ b/canvas/source/directx/dx_winstuff.hxx @@ -0,0 +1,71 @@ +/* -*- 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 . + */ + +#pragma once + +#include <algorithm> +#include <memory> + +#include <basegfx/numeric/ftools.hxx> + +#include <prewin.h> + +// Enabling Direct3D Debug Information Further more, with registry key +// \\HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Direct3D\D3D9Debugging\\EnableCreationStack +// set to 1, sets a backtrace each time an object is created to the +// following global variable: LPCWSTR CreationCallStack +#if OSL_DEBUG_LEVEL > 0 +# define D3D_DEBUG_INFO +#endif + +#include <d3d9.h> + +typedef IDirect3DSurface9 surface_type; + + +#ifndef max +#define max(a,b) (((a) > (b)) ? (a) : (b)) +#endif +#ifndef min +#define min(a,b) (((a) < (b)) ? (a) : (b)) +#endif + +#include <gdiplus.h> + +#undef max +#undef min + + +namespace dxcanvas +{ + // some shared pointer typedefs to Gdiplus objects + typedef std::shared_ptr< Gdiplus::Graphics > GraphicsSharedPtr; + typedef std::shared_ptr< Gdiplus::GraphicsPath > GraphicsPathSharedPtr; + typedef std::shared_ptr< Gdiplus::Bitmap > BitmapSharedPtr; + typedef std::shared_ptr< Gdiplus::Font > FontSharedPtr; + typedef std::shared_ptr< Gdiplus::TextureBrush > TextureBrushSharedPtr; +} + +#include <systools/win32/comtools.hxx> // for COMReference; must be inside prewin...postwin +// Attention! All DirectX factory methods return the created interfaces pre-acquired, into a raw +// pointer. Do not call AddRef on them when constructing COMReference! + +#include <postwin.h> + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/gdipluscanvas.component b/canvas/source/directx/gdipluscanvas.component new file mode 100644 index 0000000000..0ee68a1ad7 --- /dev/null +++ b/canvas/source/directx/gdipluscanvas.component @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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 . + --> + +<component loader="com.sun.star.loader.SharedLibrary" environment="@CPPU_ENV@" + xmlns="http://openoffice.org/2010/uno-components"> + <implementation name="com.sun.star.comp.rendering.BitmapCanvas.GDI+" + constructor="canvas_gdiplus_BitmapCanvas_get_implementation"> + <service name="com.sun.star.rendering.BitmapCanvas.GDI+"/> + </implementation> + <implementation name="com.sun.star.comp.rendering.Canvas.GDI+" + constructor="canvas_gdiplus_Canvas_get_implementation"> + <service name="com.sun.star.rendering.Canvas.GDI+"/> + </implementation> +</component> diff --git a/canvas/source/factory/canvasfactory.component b/canvas/source/factory/canvasfactory.component new file mode 100644 index 0000000000..3f9cabf718 --- /dev/null +++ b/canvas/source/factory/canvasfactory.component @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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 . + --> + +<component loader="com.sun.star.loader.SharedLibrary" environment="@CPPU_ENV@" + xmlns="http://openoffice.org/2010/uno-components"> + <implementation name="com.sun.star.comp.rendering.CanvasFactory" + constructor="com_sun_star_comp_rendering_CanvasFactory_get_implementation"> + <service name="com.sun.star.rendering.CanvasFactory"/> + </implementation> +</component> diff --git a/canvas/source/factory/cf_service.cxx b/canvas/source/factory/cf_service.cxx new file mode 100644 index 0000000000..a565518328 --- /dev/null +++ b/canvas/source/factory/cf_service.cxx @@ -0,0 +1,465 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> +#include <sal/log.hxx> + +#include <algorithm> +#include <mutex> +#include <utility> +#include <vector> + +#include <com/sun/star/configuration/theDefaultProvider.hpp> +#include <com/sun/star/container/XHierarchicalNameAccess.hpp> +#include <com/sun/star/container/XNameAccess.hpp> +#include <com/sun/star/lang/IllegalArgumentException.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/lang/XServiceName.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <comphelper/propertysequence.hxx> +#include <cppuhelper/implbase.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <o3tl/functional.hxx> +#include <o3tl/string_view.hxx> +#include <vcl/skia/SkiaHelper.hxx> +#include <unotools/configmgr.hxx> + +using namespace ::com::sun::star; +using namespace ::com::sun::star::uno; + + +namespace +{ + +class CanvasFactory + : public ::cppu::WeakImplHelper< lang::XServiceInfo, + lang::XMultiComponentFactory, + lang::XMultiServiceFactory > +{ + typedef std::pair< OUString, Sequence< OUString > > AvailPair; + typedef std::pair< OUString, OUString > CachePair; + typedef std::vector< AvailPair > AvailVector; + typedef std::vector< CachePair > CacheVector; + + + mutable std::mutex m_mutex; + Reference<XComponentContext> m_xContext; + Reference<container::XNameAccess> m_xCanvasConfigNameAccess; + AvailVector m_aAvailableImplementations; + AvailVector m_aAcceleratedImplementations; + AvailVector m_aAAImplementations; + mutable CacheVector m_aCachedImplementations; + mutable bool m_bCacheHasForcedLastImpl; + mutable bool m_bCacheHasUseAcceleratedEntry; + mutable bool m_bCacheHasUseAAEntry; + + void checkConfigFlag( bool& r_bFlag, + bool& r_CacheFlag, + const OUString& nodeName ) const; + Reference<XInterface> use( + OUString const & serviceName, + Sequence<Any> const & args, + Reference<XComponentContext> const & xContext ) const; + Reference<XInterface> lookupAndUse( + OUString const & serviceName, Sequence<Any> const & args, + Reference<XComponentContext> const & xContext ) const; + +public: + virtual ~CanvasFactory() override; + explicit CanvasFactory( Reference<XComponentContext> const & xContext ); + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService( OUString const & serviceName ) override; + virtual Sequence<OUString> SAL_CALL getSupportedServiceNames() override; + + // XMultiComponentFactory + virtual Sequence<OUString> SAL_CALL getAvailableServiceNames() override; + virtual Reference<XInterface> SAL_CALL createInstanceWithContext( + OUString const & name, + Reference<XComponentContext> const & xContext ) override; + virtual Reference<XInterface> SAL_CALL + createInstanceWithArgumentsAndContext( + OUString const & name, + Sequence<Any> const & args, + Reference<XComponentContext> const & xContext ) override; + + // XMultiServiceFactory + virtual Reference<XInterface> SAL_CALL createInstance( + OUString const & name ) override; + virtual Reference<XInterface> SAL_CALL createInstanceWithArguments( + OUString const & name, Sequence<Any> const & args ) override; +}; + +CanvasFactory::CanvasFactory( Reference<XComponentContext> const & xContext ) : + m_xContext(xContext), + m_bCacheHasForcedLastImpl(), + m_bCacheHasUseAcceleratedEntry(), + m_bCacheHasUseAAEntry() +{ + if (!utl::ConfigManager::IsFuzzing()) + { + try + { + // read out configuration for preferred services: + Reference<lang::XMultiServiceFactory> xConfigProvider( + configuration::theDefaultProvider::get( m_xContext ) ); + + uno::Sequence<uno::Any> aArgs(comphelper::InitAnyPropertySequence( + { + {"nodepath", uno::Any(OUString("/org.openoffice.Office.Canvas"))} + })); + m_xCanvasConfigNameAccess.set( + xConfigProvider->createInstanceWithArguments( + "com.sun.star.configuration.ConfigurationAccess", + aArgs ), + UNO_QUERY_THROW ); + + uno::Sequence<uno::Any> aArgs2(comphelper::InitAnyPropertySequence( + { + {"nodepath", uno::Any(OUString("/org.openoffice.Office.Canvas/CanvasServiceList"))} + })); + Reference<container::XNameAccess> xNameAccess( + xConfigProvider->createInstanceWithArguments( + "com.sun.star.configuration.ConfigurationAccess", + aArgs2 ), UNO_QUERY_THROW ); + Reference<container::XHierarchicalNameAccess> xHierarchicalNameAccess( + xNameAccess, UNO_QUERY_THROW); + + Sequence<OUString> serviceNames = xNameAccess->getElementNames(); + const OUString* pCurr = serviceNames.getConstArray(); + const OUString* const pEnd = pCurr + serviceNames.getLength(); + while( pCurr != pEnd ) + { + Reference<container::XNameAccess> xEntryNameAccess( + xHierarchicalNameAccess->getByHierarchicalName(*pCurr), + UNO_QUERY ); + + if( xEntryNameAccess.is() ) + { + Sequence<OUString> implementationList; + if( xEntryNameAccess->getByName("PreferredImplementations") >>= implementationList ) + { + m_aAvailableImplementations.emplace_back(*pCurr,implementationList ); + } + if( xEntryNameAccess->getByName("AcceleratedImplementations") >>= implementationList ) + { + m_aAcceleratedImplementations.emplace_back(*pCurr,implementationList ); + } + if( xEntryNameAccess->getByName("AntialiasingImplementations") >>= implementationList ) + { + m_aAAImplementations.emplace_back(*pCurr,implementationList ); + } + + } + + ++pCurr; + } + } + catch (const RuntimeException &) + { + throw; + } + catch (const Exception&) + { + } + } + + if (m_aAvailableImplementations.empty()) + { + // Ugh. Looks like configuration is borked. Fake minimal + // setup. + m_aAvailableImplementations.emplace_back(OUString("com.sun.star.rendering.Canvas"), + Sequence<OUString>{ "com.sun.star.comp.rendering.Canvas.VCL" } ); + + m_aAvailableImplementations.emplace_back(OUString("com.sun.star.rendering.SpriteCanvas"), + Sequence<OUString>{ "com.sun.star.comp.rendering.SpriteCanvas.VCL" } ); + } +} + +CanvasFactory::~CanvasFactory() +{ +} + + +// XServiceInfo +OUString CanvasFactory::getImplementationName() +{ + return "com.sun.star.comp.rendering.CanvasFactory"; +} + +sal_Bool CanvasFactory::supportsService( OUString const & serviceName ) +{ + return cppu::supportsService(this, serviceName); +} + +Sequence<OUString> CanvasFactory::getSupportedServiceNames() +{ + return { "com.sun.star.rendering.CanvasFactory" }; +} + +// XMultiComponentFactory +Sequence<OUString> CanvasFactory::getAvailableServiceNames() +{ + Sequence<OUString> aServiceNames(m_aAvailableImplementations.size()); + std::transform(m_aAvailableImplementations.begin(), + m_aAvailableImplementations.end(), + aServiceNames.getArray(), + o3tl::select1st< AvailPair >()); + return aServiceNames; +} + +Reference<XInterface> CanvasFactory::createInstanceWithContext( + OUString const & name, Reference<XComponentContext> const & xContext ) +{ + return createInstanceWithArgumentsAndContext( + name, Sequence<Any>(), xContext ); +} + + +Reference<XInterface> CanvasFactory::use( + OUString const & serviceName, + Sequence<Any> const & args, + Reference<XComponentContext> const & xContext ) const +{ + try { + return m_xContext->getServiceManager()->createInstanceWithArgumentsAndContext( + serviceName, args, xContext); + } + catch (css::lang::IllegalArgumentException &) + { + return Reference<XInterface>(); + } + catch (const RuntimeException &) + { + throw; + } + catch (const Exception &) + { + return Reference<XInterface>(); + } +} + + +void CanvasFactory::checkConfigFlag( bool& r_bFlag, + bool& r_CacheFlag, + const OUString& nodeName ) const +{ + if( m_xCanvasConfigNameAccess.is() ) + { + m_xCanvasConfigNameAccess->getByName( nodeName ) >>= r_bFlag; + + if( r_CacheFlag != r_bFlag ) + { + // cache is invalid, because of different order of + // elements + r_CacheFlag = r_bFlag; + m_aCachedImplementations.clear(); + } + } +} + + +Reference<XInterface> CanvasFactory::lookupAndUse( + OUString const & serviceName, Sequence<Any> const & args, + Reference<XComponentContext> const & xContext ) const +{ + std::scoped_lock guard(m_mutex); + + // forcing last entry from impl list, if config flag set + bool bForceLastEntry(false); + checkConfigFlag( bForceLastEntry, + m_bCacheHasForcedLastImpl, + "ForceSafeServiceImpl" ); + + // use anti-aliasing canvas, if config flag set (or not existing) + bool bUseAAEntry(true); + checkConfigFlag( bUseAAEntry, + m_bCacheHasUseAAEntry, + "UseAntialiasingCanvas" ); + + // use accelerated canvas, if config flag set (or not existing) + bool bUseAcceleratedEntry(true); + checkConfigFlag( bUseAcceleratedEntry, + m_bCacheHasUseAcceleratedEntry, + "UseAcceleratedCanvas" ); + + // try to reuse last working implementation for given service name + const CacheVector::iterator aEnd(m_aCachedImplementations.end()); + auto aMatch = std::find_if( + m_aCachedImplementations.begin(), + aEnd, + [&serviceName](CachePair const& cp) + { return serviceName == cp.first; } + ); + if( aMatch != aEnd ) { + Reference<XInterface> xCanvas( use( aMatch->second, args, xContext ) ); + if(xCanvas.is()) + return xCanvas; + } + + // lookup in available service list + const AvailVector::const_iterator aAvailEnd(m_aAvailableImplementations.end()); + auto aAvailImplsMatch = std::find_if( + m_aAvailableImplementations.begin(), + aAvailEnd, + [&serviceName](AvailPair const& ap) + { return serviceName == ap.first; } + ); + if( aAvailImplsMatch == aAvailEnd ) { + return Reference<XInterface>(); + } + + const AvailVector::const_iterator aAAEnd(m_aAAImplementations.end()); + auto aAAImplsMatch = std::find_if( + m_aAAImplementations.begin(), + aAAEnd, + [&serviceName](AvailPair const& ap) + { return serviceName == ap.first; } + ); + if( aAAImplsMatch == aAAEnd ) { + return Reference<XInterface>(); + } + + const AvailVector::const_iterator aAccelEnd(m_aAcceleratedImplementations.end()); + auto aAccelImplsMatch = std::find_if( + m_aAcceleratedImplementations.begin(), + aAccelEnd, + [&serviceName](AvailPair const& ap) + { return serviceName == ap.first; } + ); + if( aAccelImplsMatch == aAccelEnd ) { + return Reference<XInterface>(); + } + + const Sequence<OUString> aPreferredImpls( aAvailImplsMatch->second ); + const OUString* pCurrImpl = aPreferredImpls.begin(); + const OUString* const pEndImpl = aPreferredImpls.end(); + + const Sequence<OUString> aAAImpls( aAAImplsMatch->second ); + + const Sequence<OUString> aAccelImpls( aAccelImplsMatch->second ); + + // force last entry from impl list, if config flag set + if (bForceLastEntry && pCurrImpl != pEndImpl) + pCurrImpl = pEndImpl-1; + + for(; pCurrImpl != pEndImpl; ++pCurrImpl) + { + const OUString aCurrName(pCurrImpl->trim()); + + // Skia works only with vclcanvas. + if( SkiaHelper::isVCLSkiaEnabled() && !aCurrName.endsWith(".VCL")) + continue; + + // check whether given canvas service is listed in the + // sequence of "accelerated canvas implementations" + const bool bIsAcceleratedImpl( + std::any_of(aAccelImpls.begin(), aAccelImpls.end(), + [&aCurrName](OUString const& src) + { return aCurrName == o3tl::trim(src); } + )); + + // check whether given canvas service is listed in the + // sequence of "antialiasing canvas implementations" + const bool bIsAAImpl( + std::any_of(aAAImpls.begin(), aAAImpls.end(), + [&aCurrName](OUString const& src) + { return aCurrName == o3tl::trim(src); } + )); + + // try to instantiate canvas *only* if either accel and AA + // property match preference, *or*, if there's a mismatch, only + // go for a less capable canvas (that effectively let those + // pour canvas impls still work as fallbacks, should an + // accelerated/AA one fail). Property implies configuration: + // http://en.wikipedia.org/wiki/Truth_table#Logical_implication + if( (!bIsAAImpl || bUseAAEntry) && (!bIsAcceleratedImpl || bUseAcceleratedEntry) ) + { + Reference<XInterface> xCanvas(use(aCurrName, args, xContext)); + + if(xCanvas.is()) + { + if( aMatch != aEnd ) + { + // cache entry exists, replace dysfunctional + // implementation name + aMatch->second = aCurrName; + } + else + { + // new service name, add new cache entry + m_aCachedImplementations.emplace_back(serviceName, aCurrName); + } + + return xCanvas; + } + } + } + + return Reference<XInterface>(); +} + + +Reference<XInterface> CanvasFactory::createInstanceWithArgumentsAndContext( + OUString const & preferredOne, Sequence<Any> const & args, + Reference<XComponentContext> const & xContext ) +{ + Reference<XInterface> xCanvas(lookupAndUse(preferredOne, args, xContext)); + if (!xCanvas.is()) + // last resort: try service name directly + xCanvas = use(preferredOne, args, xContext); + + if (xCanvas.is()) + { + Reference<lang::XServiceName> xServiceName(xCanvas, uno::UNO_QUERY); + SAL_INFO("canvas", "using " << (xServiceName.is() ? xServiceName->getServiceName() + : OUString("(unknown)"))); + } + return xCanvas; +} + +// XMultiServiceFactory + +Reference<XInterface> CanvasFactory::createInstance( OUString const & name ) +{ + return createInstanceWithArgumentsAndContext( + name, Sequence<Any>(), m_xContext ); +} + + +Reference<XInterface> CanvasFactory::createInstanceWithArguments( + OUString const & name, Sequence<Any> const & args ) +{ + return createInstanceWithArgumentsAndContext( + name, args, m_xContext ); +} + +} // anon namespace + + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +com_sun_star_comp_rendering_CanvasFactory_get_implementation(css::uno::XComponentContext* context, + css::uno::Sequence<css::uno::Any> const &) +{ + return cppu::acquire(new CanvasFactory(context)); +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/opengl/ogl_bitmapcanvashelper.cxx b/canvas/source/opengl/ogl_bitmapcanvashelper.cxx new file mode 100644 index 0000000000..0b95dfb2a5 --- /dev/null +++ b/canvas/source/opengl/ogl_bitmapcanvashelper.cxx @@ -0,0 +1,65 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include <sal/config.h> + +#include <canvas/canvastools.hxx> + +#include "ogl_bitmapcanvashelper.hxx" + + +using namespace ::com::sun::star; + +namespace oglcanvas +{ + BitmapCanvasHelper::BitmapCanvasHelper() + {} + + void BitmapCanvasHelper::disposing() + { + CanvasHelper::disposing(); + } + + void BitmapCanvasHelper::init( rendering::XGraphicDevice& rDevice, + SpriteDeviceHelper& rDeviceHelper, + const geometry::IntegerSize2D& rSize ) + { + maSize = rSize; + CanvasHelper::init(rDevice,rDeviceHelper); + } + + uno::Reference< rendering::XBitmap > BitmapCanvasHelper::getScaledBitmap( const geometry::RealSize2D& /*newSize*/, + bool /*beFast*/ ) + { + // TODO(F1): + return uno::Reference< rendering::XBitmap >(); + } + + uno::Sequence< sal_Int8 > BitmapCanvasHelper::getData( rendering::IntegerBitmapLayout& /*bitmapLayout*/, + const geometry::IntegerRectangle2D& /*rect*/ ) + { + // TODO(F2): NYI - and improbable to ever be + return uno::Sequence< sal_Int8 >(); + } + + uno::Sequence< sal_Int8 > BitmapCanvasHelper::getPixel( rendering::IntegerBitmapLayout& /*bitmapLayout*/, + const geometry::IntegerPoint2D& /*pos*/ ) + { + // TODO(F2): NYI - and improbable to ever be + return uno::Sequence< sal_Int8 >(); + } + + rendering::IntegerBitmapLayout BitmapCanvasHelper::getMemoryLayout() const + { + return ::canvas::tools::getStdMemoryLayout(getSize()); + } + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/opengl/ogl_bitmapcanvashelper.hxx b/canvas/source/opengl/ogl_bitmapcanvashelper.hxx new file mode 100644 index 0000000000..5bdb9713d2 --- /dev/null +++ b/canvas/source/opengl/ogl_bitmapcanvashelper.hxx @@ -0,0 +1,71 @@ +/* -*- 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/. + */ + +#pragma once + +#include <com/sun/star/geometry/IntegerPoint2D.hpp> +#include <com/sun/star/geometry/IntegerRectangle2D.hpp> +#include <com/sun/star/rendering/IntegerBitmapLayout.hpp> + +#include "ogl_canvashelper.hxx" + +namespace oglcanvas +{ + /** Helper class for basic canvas functionality. */ + class BitmapCanvasHelper : public CanvasHelper + { + public: + BitmapCanvasHelper(); + + /// Release all references + void disposing(); + + /** Initialize canvas helper + + This method late-initializes the canvas helper, providing + it with the necessary device and output objects. Note that + the CanvasHelper does <em>not</em> take ownership of the + passed rDevice reference, nor does it perform any + reference counting. Thus, to prevent the reference counted + SpriteCanvas object from deletion, the user of this class + is responsible for holding ref-counted references itself! + + @param rDevice + Reference device this canvas is associated with + + */ + void init( css::rendering::XGraphicDevice& rDevice, + SpriteDeviceHelper& rDeviceHelper, + const css::geometry::IntegerSize2D& rSize ); + + // BitmapCanvasHelper functionality + // ================================ + + const css::geometry::IntegerSize2D& getSize() const { return maSize; } + + css::uno::Reference< css::rendering::XBitmap > + getScaledBitmap( const css::geometry::RealSize2D& newSize, + bool beFast ); + + css::uno::Sequence< sal_Int8 > + getData( css::rendering::IntegerBitmapLayout& bitmapLayout, + const css::geometry::IntegerRectangle2D& rect ); + + css::uno::Sequence< sal_Int8 > + getPixel( css::rendering::IntegerBitmapLayout& bitmapLayout, + const css::geometry::IntegerPoint2D& pos ); + + css::rendering::IntegerBitmapLayout getMemoryLayout() const; + + private: + css::geometry::IntegerSize2D maSize; + }; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/opengl/ogl_buffercontext.hxx b/canvas/source/opengl/ogl_buffercontext.hxx new file mode 100644 index 0000000000..d4262d3567 --- /dev/null +++ b/canvas/source/opengl/ogl_buffercontext.hxx @@ -0,0 +1,35 @@ +/* -*- 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/. + */ + +#pragma once + +#include <epoxy/gl.h> + +#include <sal/config.h> +#include <memory> + +namespace oglcanvas +{ + struct IBufferContext + { + virtual ~IBufferContext() {} + + /// start render to buffer. changes current framebuffer + virtual void startBufferRendering() = 0; + + /// end render to buffer. switches to default framebuffer + virtual void endBufferRendering() = 0; + + virtual GLuint getTextureId() = 0; + }; + + typedef std::shared_ptr<IBufferContext> IBufferContextSharedPtr; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/opengl/ogl_canvasbitmap.cxx b/canvas/source/opengl/ogl_canvasbitmap.cxx new file mode 100644 index 0000000000..0020cc39a9 --- /dev/null +++ b/canvas/source/opengl/ogl_canvasbitmap.cxx @@ -0,0 +1,54 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include <sal/config.h> + +#include <comphelper/diagnose_ex.hxx> + +#include <utility> + +#include "ogl_canvasbitmap.hxx" + + +using namespace ::com::sun::star; + +namespace oglcanvas +{ + CanvasBitmap::CanvasBitmap( const geometry::IntegerSize2D& rSize, + SpriteCanvasRef rDevice, + SpriteDeviceHelper& rDeviceHelper ) : + mpDevice(std::move( rDevice )) + { + ENSURE_OR_THROW( mpDevice.is(), + "CanvasBitmap::CanvasBitmap(): Invalid surface or device" ); + + maCanvasHelper.init( *mpDevice, rDeviceHelper, rSize ); + } + + CanvasBitmap::CanvasBitmap( const CanvasBitmap& rSrc ) : + mpDevice( rSrc.mpDevice ) + { + maCanvasHelper = rSrc.maCanvasHelper; + } + + void CanvasBitmap::disposeThis() + { + mpDevice.clear(); + + // forward to parent + CanvasBitmapBaseT::disposeThis(); + } + + bool CanvasBitmap::renderRecordedActions() const + { + return maCanvasHelper.renderRecordedActions(); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/opengl/ogl_canvasbitmap.hxx b/canvas/source/opengl/ogl_canvasbitmap.hxx new file mode 100644 index 0000000000..5cc41bb192 --- /dev/null +++ b/canvas/source/opengl/ogl_canvasbitmap.hxx @@ -0,0 +1,72 @@ +/* -*- 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/. + */ + +#pragma once + +#include <cppuhelper/compbase.hxx> + +#include <com/sun/star/rendering/XBitmapCanvas.hpp> +#include <com/sun/star/rendering/XIntegerBitmap.hpp> + +#include <base/integerbitmapbase.hxx> +#include <base/basemutexhelper.hxx> +#include <base/bitmapcanvasbase.hxx> + +#include "ogl_bitmapcanvashelper.hxx" +#include "ogl_spritecanvas.hxx" + + +/* Definition of CanvasBitmap class */ + +namespace oglcanvas +{ + typedef ::cppu::WeakComponentImplHelper< css::rendering::XBitmapCanvas, + css::rendering::XIntegerBitmap > CanvasBitmapBase_Base; + typedef ::canvas::IntegerBitmapBase< + canvas::BitmapCanvasBase2< + ::canvas::BaseMutexHelper< CanvasBitmapBase_Base >, + BitmapCanvasHelper, + ::osl::MutexGuard, + ::cppu::OWeakObject> > CanvasBitmapBaseT; + + class CanvasBitmap : public CanvasBitmapBaseT + { + public: + /** Create a canvas bitmap for the given surface + + @param rSize + Size of the bitmap + + @param rDevice + Reference device, with which bitmap should be compatible + */ + CanvasBitmap( const css::geometry::IntegerSize2D& rSize, + SpriteCanvasRef rDevice, + SpriteDeviceHelper& rDeviceHelper ); + + /** Create verbatim copy (including all recorded actions) + */ + CanvasBitmap( const CanvasBitmap& rSrc ); + + /// Dispose all internal references + virtual void disposeThis() override; + + /** Write out recorded actions + */ + bool renderRecordedActions() const; + + private: + /** MUST hold here, too, since CanvasHelper only contains a + raw pointer (without refcounting) + */ + SpriteCanvasRef mpDevice; + }; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/opengl/ogl_canvascustomsprite.cxx b/canvas/source/opengl/ogl_canvascustomsprite.cxx new file mode 100644 index 0000000000..b8ee8a5cb8 --- /dev/null +++ b/canvas/source/opengl/ogl_canvascustomsprite.cxx @@ -0,0 +1,257 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include <sal/config.h> + +#include <epoxy/gl.h> + +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/point/b2dpoint.hxx> +#include <basegfx/polygon/b2dpolypolygon.hxx> +#include <basegfx/polygon/b2dpolygontriangulator.hxx> +#include <basegfx/utils/canvastools.hxx> +#include <canvas/canvastools.hxx> +#include <o3tl/safeint.hxx> +#include <verifyinput.hxx> +#include <comphelper/diagnose_ex.hxx> + +#include "ogl_canvascustomsprite.hxx" +#include "ogl_canvastools.hxx" +#include "ogl_tools.hxx" + +using namespace ::com::sun::star; + +namespace oglcanvas +{ + CanvasCustomSprite::CanvasCustomSprite( const css::geometry::RealSize2D& rSpriteSize, + const SpriteCanvasRef& rRefDevice, + SpriteDeviceHelper& rDeviceHelper ) : + mpSpriteCanvas( rRefDevice ), + maSize(rSpriteSize), + mfAlpha(0.0), + mfPriority(0.0) + { + ENSURE_OR_THROW( rRefDevice, + "CanvasCustomSprite::CanvasCustomSprite(): Invalid sprite canvas" ); + + ::canvas::tools::setIdentityAffineMatrix2D(maTransformation); + maCanvasHelper.init( *rRefDevice, + rDeviceHelper ); + } + + void CanvasCustomSprite::disposeThis() + { + ::osl::MutexGuard aGuard( m_aMutex ); + + mpSpriteCanvas.clear(); + + // forward to parent + CanvasCustomSpriteBaseT::disposeThis(); + } + + void SAL_CALL CanvasCustomSprite::setAlpha( double alpha ) + { + canvas::tools::verifyRange( alpha, 0.0, 1.0 ); + + ::osl::MutexGuard aGuard( m_aMutex ); + mfAlpha = alpha; + } + + void SAL_CALL CanvasCustomSprite::move( const geometry::RealPoint2D& aNewPos, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState ) + { + canvas::tools::verifyArgs(aNewPos, viewState, renderState, + __func__, + getXWeak()); + + ::osl::MutexGuard aGuard( m_aMutex ); + ::basegfx::B2DHomMatrix aTransform; + ::canvas::tools::mergeViewAndRenderTransform(aTransform, + viewState, + renderState); + + // convert position to device pixel + maPosition = ::basegfx::unotools::b2DPointFromRealPoint2D(aNewPos); + maPosition *= aTransform; + } + + void SAL_CALL CanvasCustomSprite::transform( const geometry::AffineMatrix2D& aTransformation ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + maTransformation = aTransformation; + } + + void SAL_CALL CanvasCustomSprite::clip( const uno::Reference< rendering::XPolyPolygon2D >& xClip ) + { + mxClip = xClip; + } + + void SAL_CALL CanvasCustomSprite::setPriority( double nPriority ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + mfPriority = nPriority; + } + + void SAL_CALL CanvasCustomSprite::show() + { + ::osl::MutexGuard aGuard( m_aMutex ); + if( mpSpriteCanvas.is() ) + mpSpriteCanvas->show(this); + } + + void SAL_CALL CanvasCustomSprite::hide() + { + ::osl::MutexGuard aGuard( m_aMutex ); + if( mpSpriteCanvas.is() ) + mpSpriteCanvas->hide(this); + } + + uno::Reference< rendering::XCanvas > SAL_CALL CanvasCustomSprite::getContentCanvas() + { + return this; + } + + bool CanvasCustomSprite::renderSprite() const + { + if( ::basegfx::fTools::equalZero( mfAlpha ) ) + return true; + + TransformationPreserver aPreserver1; + const ::basegfx::B2IVector aSpriteSizePixel( + ::canvas::tools::roundUp( maSize.Width ), + ::canvas::tools::roundUp( maSize.Height )); + + // translate sprite to output position + glTranslated(maPosition.getX(), maPosition.getY(), 0); + + { + TransformationPreserver aPreserver2; + + // apply sprite content transformation matrix + double aGLTransform[] = + { + maTransformation.m00, maTransformation.m10, 0, 0, + maTransformation.m01, maTransformation.m11, 0, 0, + 0, 0, 1, 0, + maTransformation.m02, maTransformation.m12, 0, 1 + }; + glMultMatrixd(aGLTransform); + + IBufferContextSharedPtr pBufferContext; + if( mfAlpha != 1.0 || mxClip.is() ) + { + // drafts. Need to render to temp surface before, and then + // composite that to screen + + // TODO(P3): buffer texture + pBufferContext = maCanvasHelper.getDeviceHelper()->createBufferContext(aSpriteSizePixel); + pBufferContext->startBufferRendering(); + } + + // this ends up in pBufferContext, if that one's "current" + if( !maCanvasHelper.renderRecordedActions() ) + return false; + + if( pBufferContext ) + { + // content ended up in background buffer - compose to + // screen now. Calls below switches us back to window + // context, and binds to generated, dynamic texture + pBufferContext->endBufferRendering(); + GLuint nTexture = pBufferContext->getTextureId(); + glBindTexture(GL_TEXTURE_2D, nTexture); + + glEnable(GL_TEXTURE_2D); + glTexParameteri(GL_TEXTURE_2D, + GL_TEXTURE_MIN_FILTER, + GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, + GL_TEXTURE_MAG_FILTER, + GL_NEAREST); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, + GL_ONE_MINUS_SRC_ALPHA); + + // blend against fixed vertex color; texture alpha is multiplied in + glColor4f(1,1,1,mfAlpha); + + if( mxClip.is() ) + { + const double fWidth=maSize.Width; + const double fHeight=maSize.Height; + + // TODO(P3): buffer triangulation + const ::basegfx::triangulator::B2DTriangleVector rTriangulatedPolygon( + ::basegfx::triangulator::triangulate( + ::basegfx::unotools::b2DPolyPolygonFromXPolyPolygon2D(mxClip))); + + glBegin(GL_TRIANGLES); + for( size_t i=0; i<rTriangulatedPolygon.size(); i++ ) + { + const::basegfx::triangulator::B2DTriangle& rCandidate(rTriangulatedPolygon[i]); + glTexCoord2f( + rCandidate.getA().getX()/fWidth, + rCandidate.getA().getY()/fHeight); + glVertex2d( + rCandidate.getA().getX(), + rCandidate.getA().getY()); + + glTexCoord2f( + rCandidate.getB().getX()/fWidth, + rCandidate.getB().getY()/fHeight); + glVertex2d( + rCandidate.getB().getX(), + rCandidate.getB().getY()); + + glTexCoord2f( + rCandidate.getC().getX()/fWidth, + rCandidate.getC().getY()/fHeight); + glVertex2d( + rCandidate.getC().getX(), + rCandidate.getC().getY()); + } + glEnd(); + } + else + { + const double fWidth=maSize.Width/aSpriteSizePixel.getX(); + const double fHeight=maSize.Height/aSpriteSizePixel.getY(); + + glBegin(GL_TRIANGLE_STRIP); + glTexCoord2f(0,0); glVertex2d(0,0); + glTexCoord2f(0,fHeight); glVertex2d(0, aSpriteSizePixel.getY()); + glTexCoord2f(fWidth,0); glVertex2d(aSpriteSizePixel.getX(),0); + glTexCoord2f(fWidth,fHeight); glVertex2d(aSpriteSizePixel.getX(),aSpriteSizePixel.getY()); + glEnd(); + } + + glBindTexture(GL_TEXTURE_2D, 0); + glDisable(GL_TEXTURE_2D); + } + } + + glColor4f(1,0,0,1); + glBegin(GL_LINE_STRIP); + glVertex2d(-2,-2); + glVertex2d(-2,maSize.Height+4); + glVertex2d(maSize.Width+4,maSize.Height+4); + glVertex2d(maSize.Width+4,-2); + glVertex2d(-2,-2); + glVertex2d(maSize.Width+4,maSize.Height+4); + glEnd(); + + std::vector<double> aVec { mfAlpha, mfPriority, o3tl::narrowing<double>(maCanvasHelper.getRecordedActionCount()) }; + renderOSD( aVec, 10 ); + + return true; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/opengl/ogl_canvascustomsprite.hxx b/canvas/source/opengl/ogl_canvascustomsprite.hxx new file mode 100644 index 0000000000..3cd43cf4f1 --- /dev/null +++ b/canvas/source/opengl/ogl_canvascustomsprite.hxx @@ -0,0 +1,92 @@ +/* -*- 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/. + */ + +#pragma once + +#include <cppuhelper/compbase.hxx> + +#include <com/sun/star/rendering/XCustomSprite.hpp> +#include <com/sun/star/rendering/XPolyPolygon2D.hpp> + +#include <basegfx/point/b2dpoint.hxx> + +#include <base/basemutexhelper.hxx> + +#include "ogl_spritecanvas.hxx" +#include "ogl_canvashelper.hxx" + + +namespace oglcanvas +{ + typedef ::cppu::WeakComponentImplHelper< css::rendering::XCustomSprite, + css::rendering::XCanvas > CanvasCustomSpriteBase_Base; + typedef ::canvas::CanvasBase< + ::canvas::BaseMutexHelper< CanvasCustomSpriteBase_Base >, + CanvasHelper, + ::osl::MutexGuard, + ::cppu::OWeakObject > CanvasCustomSpriteBaseT; + + /* Definition of CanvasCustomSprite class */ + + class CanvasCustomSprite : public CanvasCustomSpriteBaseT + { + public: + /** Create a custom sprite + + @param rSpriteSize + Size of the sprite in pixel + + @param rRefDevice + Associated output device + + @param rSpriteCanvas + Target canvas + + @param rDevice + Target DX device + */ + CanvasCustomSprite( const css::geometry::RealSize2D& rSpriteSize, + const SpriteCanvasRef& rRefDevice, + SpriteDeviceHelper& rDeviceHelper ); + + virtual void disposeThis() override; + + // XSprite + virtual void SAL_CALL setAlpha( double alpha ) override; + virtual void SAL_CALL move( const css::geometry::RealPoint2D& aNewPos, const css::rendering::ViewState& viewState, const css::rendering::RenderState& renderState ) override; + virtual void SAL_CALL transform( const css::geometry::AffineMatrix2D& aTransformation ) override; + virtual void SAL_CALL clip( const css::uno::Reference< css::rendering::XPolyPolygon2D >& aClip ) override; + virtual void SAL_CALL setPriority( double nPriority ) override; + virtual void SAL_CALL show() override; + virtual void SAL_CALL hide() override; + + // XCustomSprite + virtual css::uno::Reference< css::rendering::XCanvas > SAL_CALL getContentCanvas() override; + + double getPriority() const { return mfPriority; } + + /// Render sprite content at sprite position + bool renderSprite() const; + + private: + /** MUST hold here, too, since CanvasHelper only contains a + raw pointer (without refcounting) + */ + SpriteCanvasRef mpSpriteCanvas; + const css::geometry::RealSize2D maSize; + + css::uno::Reference< css::rendering::XPolyPolygon2D > mxClip; + css::geometry::AffineMatrix2D maTransformation; + ::basegfx::B2DPoint maPosition; + double mfAlpha; + double mfPriority; + }; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/opengl/ogl_canvasfont.cxx b/canvas/source/opengl/ogl_canvasfont.cxx new file mode 100644 index 0000000000..43a9d860b5 --- /dev/null +++ b/canvas/source/opengl/ogl_canvasfont.cxx @@ -0,0 +1,67 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include <sal/config.h> + +#include <com/sun/star/rendering/FontMetrics.hpp> +#include <canvas/canvastools.hxx> +#include <utility> + +#include "ogl_canvasfont.hxx" +#include "ogl_textlayout.hxx" + +using namespace ::com::sun::star; + +namespace oglcanvas +{ + CanvasFont::CanvasFont( rendering::FontRequest aFontRequest, + const uno::Sequence< beans::PropertyValue >& extraFontProperties, + const geometry::Matrix2D& fontMatrix ) : + maFontRequest(std::move( aFontRequest )), + mnEmphasisMark(0), + maFontMatrix( fontMatrix ) + { + ::canvas::tools::extractExtraFontProperties(extraFontProperties, mnEmphasisMark); + } + + uno::Reference< rendering::XTextLayout > SAL_CALL CanvasFont::createTextLayout( const rendering::StringContext& aText, + sal_Int8 nDirection, + sal_Int64 nRandomSeed ) + { + std::unique_lock aGuard( m_aMutex ); + + return new TextLayout( aText, nDirection, nRandomSeed, ImplRef( this ) ); + } + + uno::Sequence< double > SAL_CALL CanvasFont::getAvailableSizes( ) + { + // TODO + return uno::Sequence< double >(); + } + + uno::Sequence< beans::PropertyValue > SAL_CALL CanvasFont::getExtraFontProperties( ) + { + // TODO + return uno::Sequence< beans::PropertyValue >(); + } + + rendering::FontRequest SAL_CALL CanvasFont::getFontRequest( ) + { + return maFontRequest; + } + + rendering::FontMetrics SAL_CALL CanvasFont::getFontMetrics( ) + { + // TODO + return rendering::FontMetrics(); + } + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/opengl/ogl_canvasfont.hxx b/canvas/source/opengl/ogl_canvasfont.hxx new file mode 100644 index 0000000000..92e9d337b7 --- /dev/null +++ b/canvas/source/opengl/ogl_canvasfont.hxx @@ -0,0 +1,59 @@ +/* -*- 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/. + */ + +#pragma once + +#include <comphelper/compbase.hxx> + +#include <com/sun/star/rendering/XCanvasFont.hpp> +#include <com/sun/star/geometry/Matrix2D.hpp> + +#include <rtl/ref.hxx> + + +/* Definition of CanvasFont class */ + +namespace oglcanvas +{ + class SpriteCanvas; + + typedef ::comphelper::WeakComponentImplHelper< css::rendering::XCanvasFont > CanvasFontBaseT; + + class CanvasFont : public CanvasFontBaseT + { + public: + typedef rtl::Reference<CanvasFont> ImplRef; + + /// make noncopyable + CanvasFont(const CanvasFont&) = delete; + const CanvasFont& operator=(const CanvasFont&) = delete; + + CanvasFont( css::rendering::FontRequest fontRequest, + const css::uno::Sequence< css::beans::PropertyValue >& extraFontProperties, + const css::geometry::Matrix2D& fontMatrix ); + + // XCanvasFont + virtual css::uno::Reference< css::rendering::XTextLayout > SAL_CALL createTextLayout( const css::rendering::StringContext& aText, sal_Int8 nDirection, sal_Int64 nRandomSeed ) override; + virtual css::rendering::FontRequest SAL_CALL getFontRequest( ) override; + virtual css::rendering::FontMetrics SAL_CALL getFontMetrics( ) override; + virtual css::uno::Sequence< double > SAL_CALL getAvailableSizes( ) override; + virtual css::uno::Sequence< css::beans::PropertyValue > SAL_CALL getExtraFontProperties( ) override; + + const css::geometry::Matrix2D& getFontMatrix() const { return maFontMatrix; } + + sal_uInt32 getEmphasisMark() const { return mnEmphasisMark; } + + private: + css::rendering::FontRequest maFontRequest; + sal_uInt32 mnEmphasisMark; + css::geometry::Matrix2D maFontMatrix; + }; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/opengl/ogl_canvashelper.cxx b/canvas/source/opengl/ogl_canvashelper.cxx new file mode 100644 index 0000000000..47d46a9dfb --- /dev/null +++ b/canvas/source/opengl/ogl_canvashelper.cxx @@ -0,0 +1,953 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include <sal/config.h> + +#include <memory> +#include <functional> +#include <epoxy/gl.h> + +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/polygon/b2dpolypolygon.hxx> +#include <basegfx/polygon/b2dpolypolygontools.hxx> +#include <basegfx/utils/canvastools.hxx> +#include <com/sun/star/rendering/CompositeOperation.hpp> +#include <rtl/crc.h> +#include <rtl/math.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <vcl/font.hxx> +#include <vcl/kernarray.hxx> +#include <vcl/metric.hxx> +#include <vcl/virdev.hxx> + +#include "ogl_canvasbitmap.hxx" +#include "ogl_canvasfont.hxx" +#include "ogl_canvastools.hxx" +#include "ogl_texturecache.hxx" +#include "ogl_tools.hxx" + +#include "ogl_canvashelper.hxx" + +using namespace ::com::sun::star; +using namespace std::placeholders; + +namespace oglcanvas +{ + /* Concepts: + ========= + + This OpenGL canvas implementation tries to keep all render + output as high-level as possible, i.e. geometry data and + externally-provided bitmaps. Therefore, calls at the + XCanvas-interfaces are not immediately transformed into colored + pixel inside some GL buffer, but are retained simply with their + call parameters. Only after XSpriteCanvas::updateScreen() has + been called, this all gets transferred to the OpenGL subsystem + and converted to a visible scene. The big advantage is, this + makes sprite modifications practically zero-overhead, and saves + a lot on texture memory (compared to the directx canvas, which + immediately dumps every render call into a texture). + + The drawback, of course, is that complex images churn a lot of + GPU cycles on every re-rendering. + + For the while, I'll be using immediate mode, i.e. transfer data + over and over again to the OpenGL subsystem. Alternatively, + there are display lists, which at least keep the data on the + server, or even better, vertex buffers, which copy geometry + data over en bloc. + + Next todo: put polygon geometry into vertex buffer (LRU cache + necessary?) - or, rather, buffer objects! prune entries older + than one updateScreen() call) + + Text: http://www.opengl.org/resources/features/fontsurvey/ + */ + + struct CanvasHelper::Action + { + ::basegfx::B2DHomMatrix maTransform; + GLenum meSrcBlendMode; + GLenum meDstBlendMode; + rendering::ARGBColor maARGBColor; + ::basegfx::B2DPolyPolygonVector maPolyPolys; + + std::function< bool ( + const CanvasHelper&, + const ::basegfx::B2DHomMatrix&, + GLenum, + GLenum, + const rendering::ARGBColor&, + const ::basegfx::B2DPolyPolygonVector&)> maFunction; + }; + + namespace + { + bool lcl_drawLine( const CanvasHelper& /*rHelper*/, + const ::basegfx::B2DHomMatrix& rTransform, + GLenum eSrcBlend, + GLenum eDstBlend, + const rendering::ARGBColor& rColor, + const geometry::RealPoint2D& rStartPoint, + const geometry::RealPoint2D& rEndPoint ) + { + TransformationPreserver aPreserver; + setupState(rTransform, eSrcBlend, eDstBlend, rColor); + + glBegin(GL_LINES); + glVertex2d(rStartPoint.X, rStartPoint.Y); + glVertex2d(rEndPoint.X, rEndPoint.Y); + glEnd(); + + return true; + } + + bool lcl_drawPolyPolygon( const CanvasHelper& /*rHelper*/, + const ::basegfx::B2DHomMatrix& rTransform, + GLenum eSrcBlend, + GLenum eDstBlend, + const rendering::ARGBColor& rColor, + const ::basegfx::B2DPolyPolygonVector& rPolyPolygons ) + { + TransformationPreserver aPreserver; + setupState(rTransform, eSrcBlend, eDstBlend, rColor); + + for( const auto& rPoly : rPolyPolygons ) + renderPolyPolygon( rPoly ); + + return true; + } + + bool lcl_fillPolyPolygon( const CanvasHelper& /*rHelper*/, + const ::basegfx::B2DHomMatrix& rTransform, + GLenum eSrcBlend, + GLenum eDstBlend, + const rendering::ARGBColor& rColor, + const ::basegfx::B2DPolyPolygonVector& rPolyPolygons ) + { + TransformationPreserver aPreserver; + setupState(rTransform, eSrcBlend, eDstBlend, rColor); + + for( const auto& rPoly : rPolyPolygons ) + { + glBegin( GL_TRIANGLES ); + renderComplexPolyPolygon( rPoly ); + glEnd(); + } + + return true; + } + + bool lcl_fillGradientPolyPolygon( const CanvasHelper& rHelper, + const ::basegfx::B2DHomMatrix& rTransform, + GLenum eSrcBlend, + GLenum eDstBlend, + const ::canvas::ParametricPolyPolygon::Values& rValues, + const rendering::Texture& rTexture, + const ::basegfx::B2DPolyPolygonVector& rPolyPolygons ) + { + TransformationPreserver aPreserver; + setupState(rTransform, eSrcBlend, eDstBlend, rendering::ARGBColor()); + + // convert to weird canvas textur coordinate system (not + // [0,1]^2, but path coordinate system) + ::basegfx::B2DHomMatrix aTextureTransform; + ::basegfx::unotools::homMatrixFromAffineMatrix( aTextureTransform, + rTexture.AffineTransform ); + ::basegfx::B2DRange aBounds; + for( const auto& rPoly : rPolyPolygons ) + aBounds.expand( ::basegfx::utils::getRange( rPoly ) ); + aTextureTransform.translate(-aBounds.getMinX(), -aBounds.getMinY()); + aTextureTransform.scale(1/aBounds.getWidth(), 1/aBounds.getHeight()); + + const sal_Int32 nNumCols=rValues.maColors.getLength(); + uno::Sequence< rendering::ARGBColor > aColors(nNumCols); + rendering::ARGBColor* const pColors=aColors.getArray(); + rendering::ARGBColor* pCurrCol=pColors; + for( sal_Int32 i=0; i<nNumCols; ++i ) + *pCurrCol++ = rHelper.getDevice()->getDeviceColorSpace()->convertToARGB(rValues.maColors[i])[0]; + + OSL_ASSERT(nNumCols == rValues.maStops.getLength()); + + switch( rValues.meType ) + { + case ::canvas::ParametricPolyPolygon::GradientType::Linear: + rHelper.getDeviceHelper()->useLinearGradientShader(pColors, + rValues.maStops, + aTextureTransform); + break; + + case ::canvas::ParametricPolyPolygon::GradientType::Elliptical: + rHelper.getDeviceHelper()->useRadialGradientShader(pColors, + rValues.maStops, + aTextureTransform); + break; + + case ::canvas::ParametricPolyPolygon::GradientType::Rectangular: + rHelper.getDeviceHelper()->useRectangularGradientShader(pColors, + rValues.maStops, + aTextureTransform); + break; + + default: + ENSURE_OR_THROW( false, + "CanvasHelper lcl_fillGradientPolyPolygon(): Unexpected case" ); + } + + + for( const auto& rPoly : rPolyPolygons ) + { + glBegin(GL_TRIANGLES); + renderComplexPolyPolygon( rPoly ); + glEnd(); + } + + glUseProgram(0); + glLoadIdentity(); + glMatrixMode(GL_MODELVIEW); + + return true; + } + + bool lcl_drawOwnBitmap( const CanvasHelper& /*rHelper*/, + const ::basegfx::B2DHomMatrix& rTransform, + GLenum eSrcBlend, + GLenum eDstBlend, + const rendering::ARGBColor& rColor, + const CanvasBitmap& rBitmap ) + { + TransformationPreserver aPreserver; + setupState(rTransform, eSrcBlend, eDstBlend, rColor); + + return rBitmap.renderRecordedActions(); + } + + bool lcl_drawGenericBitmap( const CanvasHelper& rHelper, + const ::basegfx::B2DHomMatrix& rTransform, + GLenum eSrcBlend, + GLenum eDstBlend, + const rendering::ARGBColor& rColor, + const geometry::IntegerSize2D& rPixelSize, + const uno::Sequence<sal_Int8>& rPixelData, + sal_uInt32 nPixelCrc32 ) + { + TransformationPreserver aPreserver; + setupState(rTransform, eSrcBlend, eDstBlend, rColor); + + const unsigned int nTexId=rHelper.getDeviceHelper()->getTextureCache().getTexture( + rPixelSize, rPixelData.getConstArray(), nPixelCrc32); + + glBindTexture(GL_TEXTURE_2D, nTexId); + glEnable(GL_TEXTURE_2D); + glTexParameteri(GL_TEXTURE_2D, + GL_TEXTURE_MIN_FILTER, + GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, + GL_TEXTURE_MAG_FILTER, + GL_NEAREST); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, + GL_ONE_MINUS_SRC_ALPHA); + + // blend against fixed vertex color; texture alpha is multiplied in + glColor4f(1,1,1,1); + + glBegin(GL_TRIANGLE_STRIP); + glTexCoord2f(0,0); glVertex2d(0,0); + glTexCoord2f(0,1); glVertex2d(0, rPixelSize.Height); + glTexCoord2f(1,0); glVertex2d(rPixelSize.Width,0); + glTexCoord2f(1,1); glVertex2d(rPixelSize.Width,rPixelSize.Height); + glEnd(); + + glBindTexture(GL_TEXTURE_2D, 0); + glDisable(GL_TEXTURE_2D); + + return true; + } + + bool lcl_fillTexturedPolyPolygon( const CanvasHelper& rHelper, + const ::basegfx::B2DHomMatrix& rTransform, + GLenum eSrcBlend, + GLenum eDstBlend, + const rendering::Texture& rTexture, + const geometry::IntegerSize2D& rPixelSize, + const uno::Sequence<sal_Int8>& rPixelData, + sal_uInt32 nPixelCrc32, + const ::basegfx::B2DPolyPolygonVector& rPolyPolygons ) + { + TransformationPreserver aPreserver; + setupState(rTransform, eSrcBlend, eDstBlend, rendering::ARGBColor()); + + const unsigned int nTexId=rHelper.getDeviceHelper()->getTextureCache().getTexture( + rPixelSize, rPixelData.getConstArray(), nPixelCrc32); + + glBindTexture(GL_TEXTURE_2D, nTexId); + glEnable(GL_TEXTURE_2D); + glTexParameteri(GL_TEXTURE_2D, + GL_TEXTURE_MIN_FILTER, + GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, + GL_TEXTURE_MAG_FILTER, + GL_NEAREST); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, + GL_ONE_MINUS_SRC_ALPHA); + + // convert to weird canvas textur coordinate system (not + // [0,1]^2, but path coordinate system) + ::basegfx::B2DHomMatrix aTextureTransform; + ::basegfx::unotools::homMatrixFromAffineMatrix( aTextureTransform, + rTexture.AffineTransform ); + ::basegfx::B2DRange aBounds; + for( const auto& rPolyPolygon : rPolyPolygons ) + aBounds.expand( ::basegfx::utils::getRange( rPolyPolygon ) ); + aTextureTransform.translate(-aBounds.getMinX(), -aBounds.getMinY()); + aTextureTransform.scale(1/aBounds.getWidth(), 1/aBounds.getHeight()); + aTextureTransform.invert(); + + glMatrixMode(GL_TEXTURE); + double aTexTransform[] = + { + aTextureTransform.get(0,0), aTextureTransform.get(1,0), 0, 0, + aTextureTransform.get(0,1), aTextureTransform.get(1,1), 0, 0, + 0, 0, 1, 0, + aTextureTransform.get(0,2), aTextureTransform.get(1,2), 0, 1 + }; + glLoadMatrixd(aTexTransform); + + // blend against fixed vertex color; texture alpha is multiplied in + glColor4f(1,1,1,rTexture.Alpha); + + for( const auto& rPolyPolygon : rPolyPolygons ) + { + glBegin(GL_TRIANGLES); + renderComplexPolyPolygon( rPolyPolygon ); + glEnd(); + } + + glLoadIdentity(); + glMatrixMode(GL_MODELVIEW); + + glBindTexture(GL_TEXTURE_2D, 0); + glDisable(GL_TEXTURE_2D); + + return true; + } + } + + CanvasHelper::CanvasHelper() : + mpDevice( nullptr ), + mpDeviceHelper( nullptr ) + {} + + CanvasHelper::~CanvasHelper() + {} + + CanvasHelper& CanvasHelper::operator=( const CanvasHelper& rSrc ) + { + mpDevice = rSrc.mpDevice; + mpDeviceHelper = rSrc.mpDeviceHelper; + mpRecordedActions = rSrc.mpRecordedActions; + return *this; + } + + void CanvasHelper::disposing() + { + RecordVectorT aThrowaway; + mpRecordedActions.swap( aThrowaway ); + mpDevice = nullptr; + mpDeviceHelper = nullptr; + } + + void CanvasHelper::init( rendering::XGraphicDevice& rDevice, + SpriteDeviceHelper& rDeviceHelper ) + { + mpDevice = &rDevice; + mpDeviceHelper = &rDeviceHelper; + } + + void CanvasHelper::clear() + { + mpRecordedActions->clear(); + } + + void CanvasHelper::drawLine( const rendering::XCanvas* /*pCanvas*/, + const geometry::RealPoint2D& aStartPoint, + const geometry::RealPoint2D& aEndPoint, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState ) + { + if( mpDevice ) + { + mpRecordedActions->push_back( Action() ); + Action& rAct=mpRecordedActions->back(); + + setupGraphicsState( rAct, viewState, renderState ); + rAct.maFunction = std::bind(&lcl_drawLine, + std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5, + aStartPoint, aEndPoint); + } + } + + void CanvasHelper::drawBezier( const rendering::XCanvas* /*pCanvas*/, + const geometry::RealBezierSegment2D& aBezierSegment, + const geometry::RealPoint2D& aEndPoint, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState ) + { + if( !mpDevice ) + return; + + mpRecordedActions->push_back( Action() ); + Action& rAct=mpRecordedActions->back(); + + setupGraphicsState( rAct, viewState, renderState ); + + // TODO(F2): subdivide&render whole curve + rAct.maFunction = std::bind(&lcl_drawLine, + std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5, + geometry::RealPoint2D( + aBezierSegment.Px, + aBezierSegment.Py), + aEndPoint); + } + + uno::Reference< rendering::XCachedPrimitive > CanvasHelper::drawPolyPolygon( const rendering::XCanvas* /*pCanvas*/, + const uno::Reference< rendering::XPolyPolygon2D >& xPolyPolygon, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState ) + { + ENSURE_OR_THROW( xPolyPolygon.is(), + "CanvasHelper::drawPolyPolygon: polygon is NULL"); + + if( mpDevice ) + { + mpRecordedActions->push_back( Action() ); + Action& rAct=mpRecordedActions->back(); + + setupGraphicsState( rAct, viewState, renderState ); + rAct.maPolyPolys.push_back( + ::basegfx::unotools::b2DPolyPolygonFromXPolyPolygon2D(xPolyPolygon)); + rAct.maPolyPolys.back().makeUnique(); // own copy, for thread safety + + rAct.maFunction = &lcl_drawPolyPolygon; + } + + // TODO(P1): Provide caching here. + return uno::Reference< rendering::XCachedPrimitive >(nullptr); + } + + uno::Reference< rendering::XCachedPrimitive > CanvasHelper::strokePolyPolygon( const rendering::XCanvas* /*pCanvas*/, + const uno::Reference< rendering::XPolyPolygon2D >& xPolyPolygon, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState, + const rendering::StrokeAttributes& /*strokeAttributes*/ ) + { + ENSURE_OR_THROW( xPolyPolygon.is(), + "CanvasHelper::strokePolyPolygon: polygon is NULL"); + + if( mpDevice ) + { + mpRecordedActions->push_back( Action() ); + Action& rAct=mpRecordedActions->back(); + + setupGraphicsState( rAct, viewState, renderState ); + rAct.maPolyPolys.push_back( + ::basegfx::unotools::b2DPolyPolygonFromXPolyPolygon2D(xPolyPolygon)); + rAct.maPolyPolys.back().makeUnique(); // own copy, for thread safety + + // TODO(F3): fallback to drawPolyPolygon currently + rAct.maFunction = &lcl_drawPolyPolygon; + } + + // TODO(P1): Provide caching here. + return uno::Reference< rendering::XCachedPrimitive >(nullptr); + } + + uno::Reference< rendering::XCachedPrimitive > CanvasHelper::strokeTexturedPolyPolygon( const rendering::XCanvas* /*pCanvas*/, + const uno::Reference< rendering::XPolyPolygon2D >& /*xPolyPolygon*/, + const rendering::ViewState& /*viewState*/, + const rendering::RenderState& /*renderState*/, + const uno::Sequence< rendering::Texture >& /*textures*/, + const rendering::StrokeAttributes& /*strokeAttributes*/ ) + { + // TODO + return uno::Reference< rendering::XCachedPrimitive >(nullptr); + } + + uno::Reference< rendering::XCachedPrimitive > CanvasHelper::strokeTextureMappedPolyPolygon( const rendering::XCanvas* /*pCanvas*/, + const uno::Reference< rendering::XPolyPolygon2D >& /*xPolyPolygon*/, + const rendering::ViewState& /*viewState*/, + const rendering::RenderState& /*renderState*/, + const uno::Sequence< rendering::Texture >& /*textures*/, + const uno::Reference< geometry::XMapping2D >& /*xMapping*/, + const rendering::StrokeAttributes& /*strokeAttributes*/ ) + { + // TODO + return uno::Reference< rendering::XCachedPrimitive >(nullptr); + } + + uno::Reference< rendering::XPolyPolygon2D > CanvasHelper::queryStrokeShapes( const rendering::XCanvas* /*pCanvas*/, + const uno::Reference< rendering::XPolyPolygon2D >& /*xPolyPolygon*/, + const rendering::ViewState& /*viewState*/, + const rendering::RenderState& /*renderState*/, + const rendering::StrokeAttributes& /*strokeAttributes*/ ) + { + // TODO + return uno::Reference< rendering::XPolyPolygon2D >(nullptr); + } + + uno::Reference< rendering::XCachedPrimitive > CanvasHelper::fillPolyPolygon( const rendering::XCanvas* /*pCanvas*/, + const uno::Reference< rendering::XPolyPolygon2D >& xPolyPolygon, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState ) + { + ENSURE_OR_THROW( xPolyPolygon.is(), + "CanvasHelper::fillPolyPolygon: polygon is NULL"); + + if( mpDevice ) + { + mpRecordedActions->push_back( Action() ); + Action& rAct=mpRecordedActions->back(); + + setupGraphicsState( rAct, viewState, renderState ); + rAct.maPolyPolys.push_back( + ::basegfx::unotools::b2DPolyPolygonFromXPolyPolygon2D(xPolyPolygon)); + rAct.maPolyPolys.back().makeUnique(); // own copy, for thread safety + + rAct.maFunction = &lcl_fillPolyPolygon; + } + + // TODO(P1): Provide caching here. + return uno::Reference< rendering::XCachedPrimitive >(nullptr); + } + + uno::Reference< rendering::XCachedPrimitive > CanvasHelper::fillTexturedPolyPolygon( const rendering::XCanvas* /*pCanvas*/, + const uno::Reference< rendering::XPolyPolygon2D >& xPolyPolygon, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState, + const uno::Sequence< rendering::Texture >& textures ) + { + ENSURE_OR_THROW( xPolyPolygon.is(), + "CanvasHelper::fillPolyPolygon: polygon is NULL"); + + if( mpDevice ) + { + mpRecordedActions->push_back( Action() ); + Action& rAct=mpRecordedActions->back(); + + setupGraphicsState( rAct, viewState, renderState ); + rAct.maPolyPolys.push_back( + ::basegfx::unotools::b2DPolyPolygonFromXPolyPolygon2D(xPolyPolygon)); + rAct.maPolyPolys.back().makeUnique(); // own copy, for thread safety + + // TODO(F1): Multi-texturing + if( textures[0].Gradient.is() ) + { + // try to cast XParametricPolyPolygon2D reference to + // our implementation class. + ::canvas::ParametricPolyPolygon* pGradient = + dynamic_cast< ::canvas::ParametricPolyPolygon* >( textures[0].Gradient.get() ); + + if( pGradient ) + { + // copy state from Gradient polypoly locally + // (given object might change!) + const ::canvas::ParametricPolyPolygon::Values& rValues( + pGradient->getValues() ); + + rAct.maFunction = std::bind(&lcl_fillGradientPolyPolygon, + std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, + rValues, + textures[0], + std::placeholders::_6); + } + else + { + // TODO(F1): The generic case is missing here + ENSURE_OR_THROW( false, + "CanvasHelper::fillTexturedPolyPolygon(): unknown parametric polygon encountered" ); + } + } + else if( textures[0].Bitmap.is() ) + { + // own bitmap? + CanvasBitmap* pOwnBitmap=dynamic_cast<CanvasBitmap*>(textures[0].Bitmap.get()); + if( pOwnBitmap ) + { + // TODO(F2): own texture bitmap + } + else + { + // TODO(P3): Highly inefficient - simply copies pixel data + + uno::Reference< rendering::XIntegerReadOnlyBitmap > xIntegerBitmap( + textures[0].Bitmap, + uno::UNO_QUERY); + if( xIntegerBitmap.is() ) + { + const geometry::IntegerSize2D aSize=xIntegerBitmap->getSize(); + rendering::IntegerBitmapLayout aLayout; + uno::Sequence<sal_Int8> aPixelData= + xIntegerBitmap->getData( + aLayout, + geometry::IntegerRectangle2D(0,0,aSize.Width,aSize.Height)); + + // force-convert color to ARGB8888 int color space + uno::Sequence<sal_Int8> aARGBBytes( + aLayout.ColorSpace->convertToIntegerColorSpace( + aPixelData, + canvas::tools::getStdColorSpace())); + + rAct.maFunction = std::bind(&lcl_fillTexturedPolyPolygon, + std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, + textures[0], + aSize, + aARGBBytes, + rtl_crc32(0, + aARGBBytes.getConstArray(), + aARGBBytes.getLength()), + std::placeholders::_6); + } + // TODO(F1): handle non-integer case + } + } + } + + // TODO(P1): Provide caching here. + return uno::Reference< rendering::XCachedPrimitive >(nullptr); + } + + uno::Reference< rendering::XCachedPrimitive > CanvasHelper::fillTextureMappedPolyPolygon( const rendering::XCanvas* /*pCanvas*/, + const uno::Reference< rendering::XPolyPolygon2D >& /*xPolyPolygon*/, + const rendering::ViewState& /*viewState*/, + const rendering::RenderState& /*renderState*/, + const uno::Sequence< rendering::Texture >& /*textures*/, + const uno::Reference< geometry::XMapping2D >& /*xMapping*/ ) + { + // TODO + return uno::Reference< rendering::XCachedPrimitive >(nullptr); + } + + uno::Reference< rendering::XCanvasFont > CanvasHelper::createFont( const rendering::XCanvas* /*pCanvas*/, + const rendering::FontRequest& fontRequest, + const uno::Sequence< beans::PropertyValue >& extraFontProperties, + const geometry::Matrix2D& fontMatrix ) + { + if( mpDevice ) + return uno::Reference< rendering::XCanvasFont >( + new CanvasFont(fontRequest, extraFontProperties, fontMatrix ) ); + + return uno::Reference< rendering::XCanvasFont >(); + } + + uno::Sequence< rendering::FontInfo > CanvasHelper::queryAvailableFonts( const rendering::XCanvas* /*pCanvas*/, + const rendering::FontInfo& /*aFilter*/, + const uno::Sequence< beans::PropertyValue >& /*aFontProperties*/ ) + { + // TODO + return uno::Sequence< rendering::FontInfo >(); + } + + uno::Reference< rendering::XCachedPrimitive > CanvasHelper::drawText( const rendering::XCanvas* /*pCanvas*/, + const rendering::StringContext& /*text*/, + const uno::Reference< rendering::XCanvasFont >& /*xFont*/, + const rendering::ViewState& /*viewState*/, + const rendering::RenderState& /*renderState*/, + sal_Int8 /*textDirection*/ ) + { + // TODO - but not used from slideshow + return uno::Reference< rendering::XCachedPrimitive >(nullptr); + } + + uno::Reference< rendering::XCachedPrimitive > CanvasHelper::drawTextLayout( const rendering::XCanvas* /*pCanvas*/, + const uno::Reference< rendering::XTextLayout >& xLayoutetText, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState ) + { + ENSURE_OR_THROW( xLayoutetText.is(), + "CanvasHelper::drawTextLayout: text is NULL"); + + if( mpDevice ) + { + ScopedVclPtrInstance< VirtualDevice > pVDev; + pVDev->EnableOutput(false); + + auto pLayoutFont = xLayoutetText->getFont(); + CanvasFont* pFont=dynamic_cast<CanvasFont*>(pLayoutFont.get()); + const rendering::StringContext& rTxt=xLayoutetText->getText(); + if( pFont && rTxt.Length ) + { + // create the font + const rendering::FontRequest& rFontRequest = pFont->getFontRequest(); + const geometry::Matrix2D& rFontMatrix = pFont->getFontMatrix(); + vcl::Font aFont( + rFontRequest.FontDescription.FamilyName, + rFontRequest.FontDescription.StyleName, + Size( 0, ::basegfx::fround(rFontRequest.CellSize))); + + aFont.SetAlignment( ALIGN_BASELINE ); + aFont.SetCharSet( (rFontRequest.FontDescription.IsSymbolFont==util::TriState_YES) ? RTL_TEXTENCODING_SYMBOL : RTL_TEXTENCODING_UNICODE ); + aFont.SetVertical( rFontRequest.FontDescription.IsVertical==util::TriState_YES ); + aFont.SetWeight( static_cast<FontWeight>(rFontRequest.FontDescription.FontDescription.Weight) ); + aFont.SetItalic( (rFontRequest.FontDescription.FontDescription.Letterform<=8) ? ITALIC_NONE : ITALIC_NORMAL ); + + if (pFont->getEmphasisMark()) + aFont.SetEmphasisMark(FontEmphasisMark(pFont->getEmphasisMark())); + + // adjust to stretched font + if(!::rtl::math::approxEqual(rFontMatrix.m00, rFontMatrix.m11)) + { + const Size aSize = pVDev->GetFontMetric( aFont ).GetFontSize(); + const double fDividend( rFontMatrix.m10 + rFontMatrix.m11 ); + double fStretch = rFontMatrix.m00 + rFontMatrix.m01; + + if( !::basegfx::fTools::equalZero( fDividend) ) + fStretch /= fDividend; + + const sal_Int32 nNewWidth = ::basegfx::fround( aSize.Width() * fStretch ); + + aFont.SetAverageFontWidth( nNewWidth ); + } + + // set font + pVDev->SetFont(aFont); + + mpRecordedActions->push_back( Action() ); + Action& rAct=mpRecordedActions->back(); + + setupGraphicsState( rAct, viewState, renderState ); + + // handle custom spacing, if there + uno::Sequence<double> aLogicalAdvancements=xLayoutetText->queryLogicalAdvancements(); + if( aLogicalAdvancements.hasElements() ) + { + // create the DXArray + const sal_Int32 nLen( aLogicalAdvancements.getLength() ); + KernArray aDXArray; + aDXArray.resize(nLen); + for( sal_Int32 i=0; i<nLen; ++i ) + aDXArray.set(i, basegfx::fround(aLogicalAdvancements[i])); + + uno::Sequence<sal_Bool> aKashidaPositions=xLayoutetText->queryKashidaPositions(); + std::span<const sal_Bool> aKashidaArray(aKashidaPositions.getConstArray(), aKashidaPositions.getLength()); + + // get the glyphs + pVDev->GetTextOutlines(rAct.maPolyPolys, + rTxt.Text, + 0, + rTxt.StartPosition, + rTxt.Length, + 0, + aDXArray, + aKashidaArray); + } + else + { + // get the glyphs + pVDev->GetTextOutlines(rAct.maPolyPolys, + rTxt.Text, + 0, + rTxt.StartPosition, + rTxt.Length ); + } + + // own copy, for thread safety + for( auto& rPoly : rAct.maPolyPolys ) + rPoly.makeUnique(); + + rAct.maFunction = &lcl_fillPolyPolygon; + } + } + + // TODO + return uno::Reference< rendering::XCachedPrimitive >(nullptr); + } + + uno::Reference< rendering::XCachedPrimitive > CanvasHelper::drawBitmap( const rendering::XCanvas* /*pCanvas*/, + const uno::Reference< rendering::XBitmap >& xBitmap, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState ) + { + ENSURE_OR_THROW( xBitmap.is(), + "CanvasHelper::drawBitmap: bitmap is NULL"); + + if( mpDevice ) + { + // own bitmap? + CanvasBitmap* pOwnBitmap=dynamic_cast<CanvasBitmap*>(xBitmap.get()); + if( pOwnBitmap ) + { + // insert as transformed copy of bitmap action vector - + // during rendering, this gets rendered into a temporary + // buffer, and then composited to the front + mpRecordedActions->push_back( Action() ); + Action& rAct=mpRecordedActions->back(); + + setupGraphicsState( rAct, viewState, renderState ); + rAct.maFunction = std::bind(&lcl_drawOwnBitmap, + std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5, + *pOwnBitmap); + } + else + { + // TODO(P3): Highly inefficient - simply copies pixel data + + uno::Reference< rendering::XIntegerReadOnlyBitmap > xIntegerBitmap( + xBitmap, uno::UNO_QUERY); + if( xIntegerBitmap.is() ) + { + const geometry::IntegerSize2D aSize=xBitmap->getSize(); + rendering::IntegerBitmapLayout aLayout; + uno::Sequence<sal_Int8> aPixelData= + xIntegerBitmap->getData( + aLayout, + geometry::IntegerRectangle2D(0,0,aSize.Width,aSize.Height)); + + // force-convert color to ARGB8888 int color space + uno::Sequence<sal_Int8> aARGBBytes( + aLayout.ColorSpace->convertToIntegerColorSpace( + aPixelData, + canvas::tools::getStdColorSpace())); + + mpRecordedActions->push_back( Action() ); + Action& rAct=mpRecordedActions->back(); + + setupGraphicsState( rAct, viewState, renderState ); + rAct.maFunction = std::bind(&lcl_drawGenericBitmap, + std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5, + aSize, aARGBBytes, + rtl_crc32(0, + aARGBBytes.getConstArray(), + aARGBBytes.getLength())); + } + // TODO(F1): handle non-integer case + } + } + + // TODO(P1): Provide caching here. + return uno::Reference< rendering::XCachedPrimitive >(nullptr); + } + + uno::Reference< rendering::XCachedPrimitive > CanvasHelper::drawBitmapModulated( const rendering::XCanvas* pCanvas, + const uno::Reference< rendering::XBitmap >& xBitmap, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState ) + { + // TODO(F3): remove this wart altogether + return drawBitmap(pCanvas, xBitmap, viewState, renderState); + } + + + void CanvasHelper::setupGraphicsState( Action& o_action, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState ) + { + ENSURE_OR_THROW( mpDevice, + "CanvasHelper::setupGraphicsState: reference device invalid" ); + + // TODO(F3): clipping + // TODO(P2): think about caching transformations between canvas calls + + // setup overall transform only now. View clip above was + // relative to view transform + ::canvas::tools::mergeViewAndRenderTransform(o_action.maTransform, + viewState, + renderState); + // setup compositing - mapping courtesy David Reveman + // (glitz_operator.c) + switch( renderState.CompositeOperation ) + { + case rendering::CompositeOperation::OVER: + o_action.meSrcBlendMode=GL_ONE; + o_action.meDstBlendMode=GL_ONE_MINUS_SRC_ALPHA; + break; + case rendering::CompositeOperation::CLEAR: + o_action.meSrcBlendMode=GL_ZERO; + o_action.meDstBlendMode=GL_ZERO; + break; + case rendering::CompositeOperation::SOURCE: + o_action.meSrcBlendMode=GL_ONE; + o_action.meDstBlendMode=GL_ZERO; + break; + case rendering::CompositeOperation::UNDER: + case rendering::CompositeOperation::DESTINATION: + o_action.meSrcBlendMode=GL_ZERO; + o_action.meDstBlendMode=GL_ONE; + break; + case rendering::CompositeOperation::INSIDE: + o_action.meSrcBlendMode=GL_DST_ALPHA; + o_action.meDstBlendMode=GL_ZERO; + break; + case rendering::CompositeOperation::INSIDE_REVERSE: + o_action.meSrcBlendMode=GL_ONE_MINUS_DST_ALPHA; + o_action.meDstBlendMode=GL_ZERO; + break; + case rendering::CompositeOperation::OUTSIDE: + o_action.meSrcBlendMode=GL_ONE_MINUS_DST_ALPHA; + o_action.meDstBlendMode=GL_ONE; + break; + case rendering::CompositeOperation::OUTSIDE_REVERSE: + o_action.meSrcBlendMode=GL_ZERO; + o_action.meDstBlendMode=GL_ONE_MINUS_SRC_ALPHA; + break; + case rendering::CompositeOperation::ATOP: + o_action.meSrcBlendMode=GL_DST_ALPHA; + o_action.meDstBlendMode=GL_ONE_MINUS_SRC_ALPHA; + break; + case rendering::CompositeOperation::ATOP_REVERSE: + o_action.meSrcBlendMode=GL_ONE_MINUS_DST_ALPHA; + o_action.meDstBlendMode=GL_SRC_ALPHA; + break; + case rendering::CompositeOperation::XOR: + o_action.meSrcBlendMode=GL_ONE_MINUS_DST_ALPHA; + o_action.meDstBlendMode=GL_ONE_MINUS_SRC_ALPHA; + break; + case rendering::CompositeOperation::ADD: + o_action.meSrcBlendMode=GL_ONE; + o_action.meDstBlendMode=GL_ONE; + break; + case rendering::CompositeOperation::SATURATE: + o_action.meSrcBlendMode=GL_SRC_ALPHA_SATURATE; + o_action.meDstBlendMode=GL_SRC_ALPHA_SATURATE; + break; + + default: + ENSURE_OR_THROW( false, "CanvasHelper::setupGraphicsState: unexpected mode" ); + break; + } + + if (renderState.DeviceColor.hasElements()) + o_action.maARGBColor = + mpDevice->getDeviceColorSpace()->convertToARGB(renderState.DeviceColor)[0]; + } + + bool CanvasHelper::renderRecordedActions() const + { + for( const auto& rRecordedAction : *mpRecordedActions ) + { + if( !rRecordedAction.maFunction( *this, + rRecordedAction.maTransform, + rRecordedAction.meSrcBlendMode, + rRecordedAction.meDstBlendMode, + rRecordedAction.maARGBColor, + rRecordedAction.maPolyPolys ) ) + return false; + } + + return true; + } + + size_t CanvasHelper::getRecordedActionCount() const + { + return mpRecordedActions->size(); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/opengl/ogl_canvashelper.hxx b/canvas/source/opengl/ogl_canvashelper.hxx new file mode 100644 index 0000000000..49513983d1 --- /dev/null +++ b/canvas/source/opengl/ogl_canvashelper.hxx @@ -0,0 +1,219 @@ +/* -*- 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/. + */ + +#pragma once + +#include <com/sun/star/rendering/XCanvas.hpp> + +#include <o3tl/cow_wrapper.hxx> +#include <vector> + +namespace oglcanvas +{ + class SpriteDeviceHelper; + + /** Helper class for basic canvas functionality. */ + class CanvasHelper + { + public: + CanvasHelper(); + + // outline because of incomplete type Action + ~CanvasHelper(); + CanvasHelper& operator=( const CanvasHelper& ); + + /// Release all references + void disposing(); + + /** Initialize canvas helper + + This method late-initializes the canvas helper, providing + it with the necessary device and output objects. Note that + the CanvasHelper does <em>not</em> take ownership of the + passed rDevice reference, nor does it perform any + reference counting. Thus, to prevent the reference counted + SpriteCanvas object from deletion, the user of this class + is responsible for holding ref-counted references itself! + + @param rDevice + Reference device this canvas is associated with + + */ + void init( css::rendering::XGraphicDevice& rDevice, + SpriteDeviceHelper& rDeviceHelper ); + + // CanvasHelper functionality + // ========================== + + // XCanvas (only providing, not implementing the + // interface. Also note subtle method parameter differences) + void clear(); + void drawLine( const css::rendering::XCanvas* pCanvas, + const css::geometry::RealPoint2D& aStartPoint, + const css::geometry::RealPoint2D& aEndPoint, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState ); + void drawBezier( const css::rendering::XCanvas* pCanvas, + const css::geometry::RealBezierSegment2D& aBezierSegment, + const css::geometry::RealPoint2D& aEndPoint, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState ); + css::uno::Reference< css::rendering::XCachedPrimitive > + drawPolyPolygon( const css::rendering::XCanvas* pCanvas, + const css::uno::Reference< + css::rendering::XPolyPolygon2D >& xPolyPolygon, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState ); + css::uno::Reference< css::rendering::XCachedPrimitive > + strokePolyPolygon( const css::rendering::XCanvas* pCanvas, + const css::uno::Reference< + css::rendering::XPolyPolygon2D >& xPolyPolygon, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState, + const css::rendering::StrokeAttributes& strokeAttributes ); + css::uno::Reference< css::rendering::XCachedPrimitive > + strokeTexturedPolyPolygon( const css::rendering::XCanvas* pCanvas, + const css::uno::Reference< + css::rendering::XPolyPolygon2D >& xPolyPolygon, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState, + const css::uno::Sequence< + css::rendering::Texture >& textures, + const css::rendering::StrokeAttributes& strokeAttributes ); + css::uno::Reference< css::rendering::XCachedPrimitive > + strokeTextureMappedPolyPolygon( const css::rendering::XCanvas* pCanvas, + const css::uno::Reference< + css::rendering::XPolyPolygon2D >& xPolyPolygon, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState, + const css::uno::Sequence< + css::rendering::Texture >& textures, + const css::uno::Reference< + css::geometry::XMapping2D >& xMapping, + const css::rendering::StrokeAttributes& strokeAttributes ); + css::uno::Reference< css::rendering::XPolyPolygon2D > + queryStrokeShapes( const css::rendering::XCanvas* pCanvas, + const css::uno::Reference< + css::rendering::XPolyPolygon2D >& xPolyPolygon, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState, + const css::rendering::StrokeAttributes& strokeAttributes ); + css::uno::Reference< css::rendering::XCachedPrimitive > + fillPolyPolygon( const css::rendering::XCanvas* pCanvas, + const css::uno::Reference< + css::rendering::XPolyPolygon2D >& xPolyPolygon, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState ); + css::uno::Reference< css::rendering::XCachedPrimitive > + fillTexturedPolyPolygon( const css::rendering::XCanvas* pCanvas, + const css::uno::Reference< + css::rendering::XPolyPolygon2D >& xPolyPolygon, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState, + const css::uno::Sequence< + css::rendering::Texture >& textures ); + css::uno::Reference< css::rendering::XCachedPrimitive > + fillTextureMappedPolyPolygon( const css::rendering::XCanvas* pCanvas, + const css::uno::Reference< + css::rendering::XPolyPolygon2D >& xPolyPolygon, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState, + const css::uno::Sequence< + css::rendering::Texture >& textures, + const css::uno::Reference< + css::geometry::XMapping2D >& xMapping ); + + css::uno::Reference< css::rendering::XCanvasFont > + createFont( const css::rendering::XCanvas* pCanvas, + const css::rendering::FontRequest& fontRequest, + const css::uno::Sequence< + css::beans::PropertyValue >& extraFontProperties, + const css::geometry::Matrix2D& fontMatrix ); + + css::uno::Sequence< css::rendering::FontInfo > + queryAvailableFonts( const css::rendering::XCanvas* pCanvas, + const css::rendering::FontInfo& aFilter, + const css::uno::Sequence< + css::beans::PropertyValue >& aFontProperties ); + + css::uno::Reference< css::rendering::XCachedPrimitive > + drawText( const css::rendering::XCanvas* pCanvas, + const css::rendering::StringContext& text, + const css::uno::Reference< + css::rendering::XCanvasFont >& xFont, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState, + sal_Int8 textDirection ); + + css::uno::Reference< css::rendering::XCachedPrimitive > + drawTextLayout( const css::rendering::XCanvas* pCanvas, + const css::uno::Reference< + css::rendering::XTextLayout >& layoutetText, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState ); + + css::uno::Reference< css::rendering::XCachedPrimitive > + drawBitmap( const css::rendering::XCanvas* pCanvas, + const css::uno::Reference< + css::rendering::XBitmap >& xBitmap, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState ); + css::uno::Reference< css::rendering::XCachedPrimitive > + drawBitmapModulated( const css::rendering::XCanvas* pCanvas, + const css::uno::Reference< + css::rendering::XBitmap >& xBitmap, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState ); + css::uno::Reference< css::rendering::XGraphicDevice > + getDevice() { return css::uno::Reference< css::rendering::XGraphicDevice >(mpDevice); } + + /** Write out recorded actions + */ + bool renderRecordedActions() const; + + /** Retrieve number of recorded actions + */ + size_t getRecordedActionCount() const; + + SpriteDeviceHelper* getDeviceHelper() const { return mpDeviceHelper; } + css::rendering::XGraphicDevice* getDevice() const { return mpDevice; } + + struct Action; + typedef o3tl::cow_wrapper< std::vector<Action>, + o3tl::ThreadSafeRefCountingPolicy > RecordVectorT; + + private: + CanvasHelper( const CanvasHelper& ) = delete; + + void setupGraphicsState( Action& o_action, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState ); + + /** Phyical output device + + Deliberately not a refcounted reference, because of + potential circular references for spritecanvas. + */ + css::rendering::XGraphicDevice* mpDevice; + + /** Internal helper - used for a few global GL objects, + e.g. shader programs; and caches + */ + SpriteDeviceHelper* mpDeviceHelper; + + /** Ptr to array of recorded render calls + + Gets shared copy-on-write, when this CanvasHelper is + copied (used e.g. for CanvasBitmap) + */ + RecordVectorT mpRecordedActions; + }; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/opengl/ogl_canvastools.cxx b/canvas/source/opengl/ogl_canvastools.cxx new file mode 100644 index 0000000000..97e7377c0a --- /dev/null +++ b/canvas/source/opengl/ogl_canvastools.cxx @@ -0,0 +1,148 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include <sal/config.h> + +#include <epoxy/gl.h> + +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/polygon/b2dpolygontriangulator.hxx> +#include <basegfx/polygon/b2dpolypolygon.hxx> +#include <basegfx/utils/tools.hxx> +#include <com/sun/star/rendering/ARGBColor.hpp> + +#include "ogl_canvastools.hxx" + +using namespace ::com::sun::star; + +namespace oglcanvas +{ + /// triangulates polygon before + void renderComplexPolyPolygon( const ::basegfx::B2DPolyPolygon& rPolyPoly ) + { + ::basegfx::B2DPolyPolygon aPolyPoly(rPolyPoly); + if( aPolyPoly.areControlPointsUsed() ) + aPolyPoly = rPolyPoly.getDefaultAdaptiveSubdivision(); + const ::basegfx::B2DRange& rBounds(aPolyPoly.getB2DRange()); + const double nWidth=rBounds.getWidth(); + const double nHeight=rBounds.getHeight(); + const ::basegfx::triangulator::B2DTriangleVector rTriangulatedPolygon( + ::basegfx::triangulator::triangulate(aPolyPoly)); + + for( auto const& rTriangulatedPolygonItem : rTriangulatedPolygon ) + { + const::basegfx::triangulator::B2DTriangle& rCandidate(rTriangulatedPolygonItem); + glTexCoord2f( + rCandidate.getA().getX()/nWidth, + rCandidate.getA().getY()/nHeight); + glVertex2d( + rCandidate.getA().getX(), + rCandidate.getA().getY()); + + glTexCoord2f( + rCandidate.getB().getX()/nWidth, + rCandidate.getB().getY()/nHeight); + glVertex2d( + rCandidate.getB().getX(), + rCandidate.getB().getY()); + + glTexCoord2f( + rCandidate.getC().getX()/nWidth, + rCandidate.getC().getY()/nHeight); + glVertex2d( + rCandidate.getC().getX(), + rCandidate.getC().getY()); + } + } + + /** only use this for line polygons. + + better not leave triangulation to OpenGL. also, ignores texturing + */ + void renderPolyPolygon( const ::basegfx::B2DPolyPolygon& rPolyPoly ) + { + ::basegfx::B2DPolyPolygon aPolyPoly(rPolyPoly); + if( aPolyPoly.areControlPointsUsed() ) + aPolyPoly = rPolyPoly.getDefaultAdaptiveSubdivision(); + + for( sal_uInt32 i=0; i<aPolyPoly.count(); i++ ) + { + glBegin(GL_LINE_STRIP); + + const ::basegfx::B2DPolygon& rPolygon( aPolyPoly.getB2DPolygon(i) ); + + const sal_uInt32 nPts=rPolygon.count(); + const sal_uInt32 nExtPts=nPts + int(rPolygon.isClosed()); + for( sal_uInt32 j=0; j<nExtPts; j++ ) + { + const ::basegfx::B2DPoint& rPt( rPolygon.getB2DPoint( j % nPts ) ); + glVertex2d(rPt.getX(), rPt.getY()); + } + + glEnd(); + } + } + + void setupState( const ::basegfx::B2DHomMatrix& rTransform, + GLenum eSrcBlend, + GLenum eDstBlend, + const rendering::ARGBColor& rColor ) + { + double aGLTransform[] = + { + rTransform.get(0,0), rTransform.get(1,0), 0, 0, + rTransform.get(0,1), rTransform.get(1,1), 0, 0, + 0, 0, 1, 0, + rTransform.get(0,2), rTransform.get(1,2), 0, 1 + }; + glMultMatrixd(aGLTransform); + + glEnable(GL_BLEND); + glBlendFunc(eSrcBlend, eDstBlend); + + glColor4d(rColor.Red, + rColor.Green, + rColor.Blue, + rColor.Alpha); + + // GL 1.2: + // glBlendEquation( GLenum mode ); + // glBlendColor( GLclampf red, GLclampf green,GLclampf blue, GLclampf alpha ); + // glConvolutionFilter1D + // glConvolutionFilter2D + // glSeparableFilter2D + } + + void renderOSD( const std::vector<double>& rNumbers, double scale ) + { + double y=4.0; + basegfx::B2DHomMatrix aTmp; + basegfx::B2DHomMatrix aScaleShear; + aScaleShear.shearX(-0.1); + aScaleShear.scale(scale,scale); + + for(double rNumber : rNumbers) + { + aTmp.identity(); + aTmp.translate(0,y); + y += 1.2*scale; + + basegfx::B2DPolyPolygon aPoly= + basegfx::utils::number2PolyPolygon(rNumber,10,3); + + aTmp=aTmp*aScaleShear; + aPoly.transform(aTmp); + + glColor4f(0,1,0,1); + renderPolyPolygon(aPoly); + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/opengl/ogl_canvastools.hxx b/canvas/source/opengl/ogl_canvastools.hxx new file mode 100644 index 0000000000..8e7029fbe5 --- /dev/null +++ b/canvas/source/opengl/ogl_canvastools.hxx @@ -0,0 +1,37 @@ +/* -*- 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/. + */ + +#pragma once + +#include <sal/config.h> +#include <vector> + +#include <epoxy/gl.h> + +namespace com::sun::star::rendering { + struct ARGBColor; +} +namespace basegfx { + class B2DPolyPolygon; + class B2DHomMatrix; +} + +namespace oglcanvas +{ + void renderComplexPolyPolygon( const ::basegfx::B2DPolyPolygon& rPolyPoly ); + void renderPolyPolygon( const ::basegfx::B2DPolyPolygon& rPolyPoly ); + void setupState( const ::basegfx::B2DHomMatrix& rTransform, + GLenum eSrcBlend, + GLenum eDstBlend, + const com::sun::star::rendering::ARGBColor& rColor ); + + void renderOSD( const std::vector<double>& rNumbers, double scale ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/opengl/ogl_spritecanvas.cxx b/canvas/source/opengl/ogl_spritecanvas.cxx new file mode 100644 index 0000000000..2b95886675 --- /dev/null +++ b/canvas/source/opengl/ogl_spritecanvas.cxx @@ -0,0 +1,166 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include <sal/config.h> +#include <sal/log.hxx> + +#include <com/sun/star/lang/NoSupportException.hpp> +#include <osl/mutex.hxx> +#include <toolkit/helper/vclunohelper.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <vcl/opengl/OpenGLHelper.hxx> +#include <vcl/window.hxx> + +#include "ogl_canvascustomsprite.hxx" +#include "ogl_spritecanvas.hxx" + +using namespace ::com::sun::star; + +namespace oglcanvas +{ + SpriteCanvas::SpriteCanvas( const uno::Sequence< uno::Any >& aArguments, + const uno::Reference< uno::XComponentContext >& /*rxContext*/ ) : + maArguments(aArguments) + { + } + + void SpriteCanvas::initialize() + { + // Only call initialize when not in probe mode + if( !maArguments.hasElements() ) + return; + + SAL_INFO("canvas.ogl", "SpriteCanvas::initialize called" ); + + /* aArguments: + 0: ptr to creating instance (Window or VirtualDevice) + 1: current bounds of creating instance + 2: bool, denoting always on top state for Window (always false for VirtualDevice) + 3: XWindow for creating Window (or empty for VirtualDevice) + 4: SystemGraphicsData as a streamed Any + */ + ENSURE_ARG_OR_THROW( maArguments.getLength() >= 4 && + maArguments[3].getValueTypeClass() == uno::TypeClass_INTERFACE, + "OpenGL SpriteCanvas::initialize: wrong number of arguments, or wrong types" ); + + uno::Reference< awt::XWindow > xParentWindow; + maArguments[3] >>= xParentWindow; + VclPtr<vcl::Window> pParentWindow = VCLUnoHelper::GetWindow(xParentWindow); + if( !pParentWindow ) + throw lang::NoSupportException( + "Parent window not VCL window, or canvas out-of-process!", nullptr); + + awt::Rectangle aRect; + maArguments[1] >>= aRect; + + // setup helper + maDeviceHelper.init( *pParentWindow, + *this, + aRect ); + maCanvasHelper.init( *this, maDeviceHelper ); + maArguments.realloc(0); + } + + void SpriteCanvas::disposeThis() + { + ::osl::MutexGuard aGuard( m_aMutex ); + + // forward to parent + SpriteCanvasBaseT::disposeThis(); + } + + sal_Bool SAL_CALL SpriteCanvas::showBuffer( sal_Bool bUpdateAll ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + + // avoid repaints on hidden window (hidden: not mapped to + // screen). Return failure, since the screen really has _not_ + // been updated (caller should try again later) + return mbIsVisible && SpriteCanvasBaseT::showBuffer( bUpdateAll ); + } + + sal_Bool SAL_CALL SpriteCanvas::switchBuffer( sal_Bool bUpdateAll ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + + // avoid repaints on hidden window (hidden: not mapped to + // screen). Return failure, since the screen really has _not_ + // been updated (caller should try again later) + return mbIsVisible && SpriteCanvasBaseT::switchBuffer( bUpdateAll ); + } + + uno::Reference< rendering::XAnimatedSprite > SAL_CALL SpriteCanvas::createSpriteFromAnimation( + const uno::Reference< rendering::XAnimation >& /*animation*/ ) + { + return uno::Reference< rendering::XAnimatedSprite >(); + } + + uno::Reference< rendering::XAnimatedSprite > SAL_CALL SpriteCanvas::createSpriteFromBitmaps( + const uno::Sequence< uno::Reference< rendering::XBitmap > >& /*animationBitmaps*/, + ::sal_Int8 /*interpolationMode*/ ) + { + return uno::Reference< rendering::XAnimatedSprite >(); + } + + uno::Reference< rendering::XCustomSprite > SAL_CALL SpriteCanvas::createCustomSprite( + const geometry::RealSize2D& spriteSize ) + { + return uno::Reference< rendering::XCustomSprite >( + new CanvasCustomSprite(spriteSize, this, maDeviceHelper) ); + } + + uno::Reference< rendering::XSprite > SAL_CALL SpriteCanvas::createClonedSprite( + const uno::Reference< rendering::XSprite >& /*original*/ ) + { + return uno::Reference< rendering::XSprite >(); + } + + sal_Bool SAL_CALL SpriteCanvas::updateScreen(sal_Bool bUpdateAll) + { + ::osl::MutexGuard aGuard( m_aMutex ); + return maDeviceHelper.showBuffer(mbIsVisible, bUpdateAll); + } + + OUString SAL_CALL SpriteCanvas::getServiceName( ) + { + return "com.sun.star.rendering.SpriteCanvas.OGL"; + } + + void SpriteCanvas::show( const ::rtl::Reference< CanvasCustomSprite >& xSprite ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + maDeviceHelper.show(xSprite); + } + + void SpriteCanvas::hide( const ::rtl::Reference< CanvasCustomSprite >& xSprite ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + maDeviceHelper.hide(xSprite); + } + + void SpriteCanvas::renderRecordedActions() const + { + maCanvasHelper.renderRecordedActions(); + } + +} + + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +com_sun_star_comp_rendering_SpriteCanvas_OGL_get_implementation( + css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const& args) +{ + if( !OpenGLHelper::supportsOpenGL()) + return nullptr; + rtl::Reference<oglcanvas::SpriteCanvas> p = new oglcanvas::SpriteCanvas(args, context); + p->initialize(); + return cppu::acquire(p.get()); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/opengl/ogl_spritecanvas.hxx b/canvas/source/opengl/ogl_spritecanvas.hxx new file mode 100644 index 0000000000..33948b2d0c --- /dev/null +++ b/canvas/source/opengl/ogl_spritecanvas.hxx @@ -0,0 +1,114 @@ +/* -*- 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/. + */ + +#pragma once + +#include <rtl/ref.hxx> + +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/lang/XServiceName.hpp> +#include <com/sun/star/awt/XWindowListener.hpp> +#include <com/sun/star/util/XUpdatable.hpp> +#include <com/sun/star/rendering/XSpriteCanvas.hpp> +#include <com/sun/star/rendering/XGraphicDevice.hpp> +#include <com/sun/star/rendering/XBufferController.hpp> + +#include <cppuhelper/compbase.hxx> +#include <comphelper/uno3.hxx> + +#include <base/canvasbase.hxx> +#include <base/disambiguationhelper.hxx> +#include <base/bufferedgraphicdevicebase.hxx> + +#include "ogl_spritedevicehelper.hxx" +#include "ogl_canvashelper.hxx" + + +namespace oglcanvas +{ + class CanvasCustomSprite; + + typedef ::cppu::WeakComponentImplHelper< css::rendering::XSpriteCanvas, + css::rendering::XGraphicDevice, + css::lang::XMultiServiceFactory, + css::rendering::XBufferController, + css::awt::XWindowListener, + css::util::XUpdatable, + css::beans::XPropertySet, + css::lang::XServiceName > WindowGraphicDeviceBase_Base; + typedef ::canvas::BufferedGraphicDeviceBase< + ::canvas::DisambiguationHelper< WindowGraphicDeviceBase_Base >, + SpriteDeviceHelper, + ::osl::MutexGuard, + ::cppu::OWeakObject > SpriteCanvasDeviceBaseT; + + typedef ::canvas::CanvasBase< SpriteCanvasDeviceBaseT, + CanvasHelper, + ::osl::MutexGuard, + ::cppu::OWeakObject > SpriteCanvasBaseT; + + /** Product of this component's factory. + + The SpriteCanvas object combines the actual Window canvas with + the XGraphicDevice interface. This is because there's a + one-to-one relation between them, anyway, since each window + can have exactly one canvas and one associated + XGraphicDevice. And to avoid messing around with circular + references, this is implemented as one single object. + */ + class SpriteCanvas : public SpriteCanvasBaseT + { + public: + SpriteCanvas( const css::uno::Sequence< + css::uno::Any >& aArguments, + const css::uno::Reference< + css::uno::XComponentContext >& rxContext ); + + void initialize(); + + /// Dispose all internal references + virtual void disposeThis() override; + + // Forwarding the XComponent implementation to the + // cppu::ImplHelper templated base + // Classname Base doing refcounting Base implementing the XComponent interface + // | | | + // V V V + DECLARE_UNO3_XCOMPONENT_AGG_DEFAULTS( SpriteCanvas, WindowGraphicDeviceBase_Base, ::cppu::WeakComponentImplHelperBase ) + + // XBufferController (partial) + virtual sal_Bool SAL_CALL showBuffer( sal_Bool bUpdateAll ) override; + virtual sal_Bool SAL_CALL switchBuffer( sal_Bool bUpdateAll ) override; + + // XSpriteCanvas + virtual css::uno::Reference< css::rendering::XAnimatedSprite > SAL_CALL createSpriteFromAnimation( const css::uno::Reference< css::rendering::XAnimation >& animation ) override; + virtual css::uno::Reference< css::rendering::XAnimatedSprite > SAL_CALL createSpriteFromBitmaps( const css::uno::Sequence< css::uno::Reference< css::rendering::XBitmap > >& animationBitmaps, ::sal_Int8 interpolationMode ) override; + virtual css::uno::Reference< css::rendering::XCustomSprite > SAL_CALL createCustomSprite( const css::geometry::RealSize2D& spriteSize ) override; + virtual css::uno::Reference< css::rendering::XSprite > SAL_CALL createClonedSprite( const css::uno::Reference< css::rendering::XSprite >& original ) override; + virtual sal_Bool SAL_CALL updateScreen( sal_Bool bUpdateAll ) override; + + // XServiceName + virtual OUString SAL_CALL getServiceName( ) override; + + void show( const ::rtl::Reference< CanvasCustomSprite >& ); + void hide( const ::rtl::Reference< CanvasCustomSprite >& ); + + /** Write out recorded actions + */ + void renderRecordedActions() const; + + private: + css::uno::Sequence< css::uno::Any > maArguments; + }; + + typedef ::rtl::Reference< SpriteCanvas > SpriteCanvasRef; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/opengl/ogl_spritedevicehelper.cxx b/canvas/source/opengl/ogl_spritedevicehelper.cxx new file mode 100644 index 0000000000..8cb0a6934f --- /dev/null +++ b/canvas/source/opengl/ogl_spritedevicehelper.cxx @@ -0,0 +1,549 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include <sal/config.h> +#include <sal/log.hxx> + +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/utils/canvastools.hxx> +#include <basegfx/utils/unopolypolygon.hxx> +#include <com/sun/star/awt/XTopWindow.hpp> +#include <com/sun/star/rendering/XIntegerBitmapColorSpace.hpp> +#include <com/sun/star/uno/Reference.hxx> +#include <toolkit/helper/vclunohelper.hxx> +#include <vcl/canvastools.hxx> +#include <vcl/opengl/OpenGLHelper.hxx> +#include <vcl/syschild.hxx> + +#include "ogl_spritedevicehelper.hxx" +#include "ogl_spritecanvas.hxx" +#include "ogl_canvasbitmap.hxx" +#include "ogl_canvastools.hxx" +#include "ogl_canvascustomsprite.hxx" +#include "ogl_texturecache.hxx" + +using namespace ::com::sun::star; + +static void initContext() +{ + // need the backside for mirror effects + glDisable(GL_CULL_FACE); + + // no perspective, we're 2D + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + + // misc preferences + glEnable(GL_POINT_SMOOTH); + glEnable(GL_LINE_SMOOTH); + glEnable(GL_POLYGON_SMOOTH); + glHint(GL_POINT_SMOOTH_HINT,GL_NICEST); + glHint(GL_LINE_SMOOTH_HINT,GL_NICEST); + glHint(GL_POLYGON_SMOOTH_HINT,GL_NICEST); + glShadeModel(GL_FLAT); +} + +static void initTransformation(const ::Size& rSize) +{ + // use whole window + glViewport( 0,0, + static_cast<GLsizei>(rSize.Width()), + static_cast<GLsizei>(rSize.Height()) ); + + // model coordinate system is already in device pixel + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + glTranslated(-1.0, 1.0, 0.0); + glScaled( 2.0 / rSize.Width(), + -2.0 / rSize.Height(), + 1.0 ); + + // clear to black + glClearColor(0,0,0,0); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); +} + +namespace oglcanvas +{ + + SpriteDeviceHelper::SpriteDeviceHelper() : + mpSpriteCanvas(nullptr), + mpTextureCache(std::make_shared<TextureCache>()), + mnLinearTwoColorGradientProgram(0), + mnLinearMultiColorGradientProgram(0), + mnRadialTwoColorGradientProgram(0), + mnRadialMultiColorGradientProgram(0), + mnRectangularTwoColorGradientProgram(0), + mnRectangularMultiColorGradientProgram(0), + mxContext(OpenGLContext::Create()) + {} + + SpriteDeviceHelper::~SpriteDeviceHelper() + { mxContext->dispose(); } + + void SpriteDeviceHelper::init( vcl::Window& rWindow, + SpriteCanvas& rSpriteCanvas, + const awt::Rectangle& rViewArea ) + { + mpSpriteCanvas = &rSpriteCanvas; + + rSpriteCanvas.setWindow( + uno::Reference<awt::XWindow2>( + VCLUnoHelper::GetInterface(&rWindow), + uno::UNO_QUERY_THROW) ); + + mxContext->requestLegacyContext(); + mxContext->init(&rWindow); + // init window context + initContext(); + + mnLinearMultiColorGradientProgram = + OpenGLHelper::LoadShaders("dummyVertexShader", "linearMultiColorGradientFragmentShader"); + + mnLinearTwoColorGradientProgram = + OpenGLHelper::LoadShaders("dummyVertexShader", "linearTwoColorGradientFragmentShader"); + + mnRadialMultiColorGradientProgram = + OpenGLHelper::LoadShaders("dummyVertexShader", "radialMultiColorGradientFragmentShader"); + + mnRadialTwoColorGradientProgram = + OpenGLHelper::LoadShaders("dummyVertexShader", "radialTwoColorGradientFragmentShader"); + + mnRectangularMultiColorGradientProgram = + OpenGLHelper::LoadShaders("dummyVertexShader", "rectangularMultiColorGradientFragmentShader"); + + mnRectangularTwoColorGradientProgram = + OpenGLHelper::LoadShaders("dummyVertexShader", "rectangularTwoColorGradientFragmentShader"); + + mxContext->makeCurrent(); + + notifySizeUpdate(rViewArea); + // TODO(E3): check for GL_ARB_imaging extension + } + + void SpriteDeviceHelper::disposing() + { + // release all references + mpSpriteCanvas = nullptr; + mpTextureCache.reset(); + + if( mxContext->isInitialized() ) + { + glDeleteProgram( mnRectangularTwoColorGradientProgram ); + glDeleteProgram( mnRectangularMultiColorGradientProgram ); + glDeleteProgram( mnRadialTwoColorGradientProgram ); + glDeleteProgram( mnRadialMultiColorGradientProgram ); + glDeleteProgram( mnLinearTwoColorGradientProgram ); + glDeleteProgram( mnLinearMultiColorGradientProgram ); + } + } + + geometry::RealSize2D SpriteDeviceHelper::getPhysicalResolution() + { + if( !mxContext->isInitialized() ) + return ::canvas::tools::createInfiniteSize2D(); // we're disposed + + // Map a one-by-one millimeter box to pixel + SystemChildWindow* pChildWindow = mxContext->getChildWindow(); + const MapMode aOldMapMode( pChildWindow->GetMapMode() ); + pChildWindow->SetMapMode( MapMode(MapUnit::MapMM) ); + const Size aPixelSize( pChildWindow->LogicToPixel(Size(1,1)) ); + pChildWindow->SetMapMode( aOldMapMode ); + + return vcl::unotools::size2DFromSize( aPixelSize ); + } + + geometry::RealSize2D SpriteDeviceHelper::getPhysicalSize() + { + if( !mxContext->isInitialized() ) + return ::canvas::tools::createInfiniteSize2D(); // we're disposed + + // Map the pixel dimensions of the output window to millimeter + SystemChildWindow* pChildWindow = mxContext->getChildWindow(); + const MapMode aOldMapMode( pChildWindow->GetMapMode() ); + pChildWindow->SetMapMode( MapMode(MapUnit::MapMM) ); + const Size aLogSize( pChildWindow->PixelToLogic(pChildWindow->GetOutputSizePixel()) ); + pChildWindow->SetMapMode( aOldMapMode ); + + return vcl::unotools::size2DFromSize( aLogSize ); + } + + uno::Reference< rendering::XLinePolyPolygon2D > SpriteDeviceHelper::createCompatibleLinePolyPolygon( + const uno::Reference< rendering::XGraphicDevice >& /*rDevice*/, + const uno::Sequence< uno::Sequence< geometry::RealPoint2D > >& points ) + { + // disposed? + if( !mpSpriteCanvas ) + return uno::Reference< rendering::XLinePolyPolygon2D >(); // we're disposed + + return uno::Reference< rendering::XLinePolyPolygon2D >( + new ::basegfx::unotools::UnoPolyPolygon( + ::basegfx::unotools::polyPolygonFromPoint2DSequenceSequence( points ))); + } + + uno::Reference< rendering::XBezierPolyPolygon2D > SpriteDeviceHelper::createCompatibleBezierPolyPolygon( + const uno::Reference< rendering::XGraphicDevice >& /*rDevice*/, + const uno::Sequence< uno::Sequence< geometry::RealBezierSegment2D > >& points ) + { + // disposed? + if( !mpSpriteCanvas ) + return uno::Reference< rendering::XBezierPolyPolygon2D >(); // we're disposed + + return uno::Reference< rendering::XBezierPolyPolygon2D >( + new ::basegfx::unotools::UnoPolyPolygon( + ::basegfx::unotools::polyPolygonFromBezier2DSequenceSequence( points ) ) ); + } + + uno::Reference< rendering::XBitmap > SpriteDeviceHelper::createCompatibleBitmap( + const uno::Reference< rendering::XGraphicDevice >& /*rDevice*/, + const geometry::IntegerSize2D& size ) + { + // disposed? + if( !mpSpriteCanvas ) + return uno::Reference< rendering::XBitmap >(); // we're disposed + + return uno::Reference< rendering::XBitmap >( + new CanvasBitmap( size, + mpSpriteCanvas, + *this ) ); + } + + uno::Reference< rendering::XVolatileBitmap > SpriteDeviceHelper::createVolatileBitmap( + const uno::Reference< rendering::XGraphicDevice >& /*rDevice*/, + const geometry::IntegerSize2D& /*size*/ ) + { + return uno::Reference< rendering::XVolatileBitmap >(); + } + + uno::Reference< rendering::XBitmap > SpriteDeviceHelper::createCompatibleAlphaBitmap( + const uno::Reference< rendering::XGraphicDevice >& /*rDevice*/, + const geometry::IntegerSize2D& size ) + { + // disposed? + if( !mpSpriteCanvas ) + return uno::Reference< rendering::XBitmap >(); // we're disposed + + return uno::Reference< rendering::XBitmap >( + new CanvasBitmap( size, + mpSpriteCanvas, + *this ) ); + } + + uno::Reference< rendering::XVolatileBitmap > SpriteDeviceHelper::createVolatileAlphaBitmap( + const uno::Reference< rendering::XGraphicDevice >& /*rDevice*/, + const geometry::IntegerSize2D& /*size*/ ) + { + return uno::Reference< rendering::XVolatileBitmap >(); + } + + namespace + { + /** Functor providing a StrictWeakOrdering for XSprites (over + priority) + */ + struct SpriteComparator + { + bool operator()( const ::rtl::Reference<CanvasCustomSprite>& rLHS, + const ::rtl::Reference<CanvasCustomSprite>& rRHS ) const + { + const double nPrioL( rLHS->getPriority() ); + const double nPrioR( rRHS->getPriority() ); + + // if prios are equal, tie-break on ptr value + return nPrioL == nPrioR ? rLHS.get() < rRHS.get() : nPrioL < nPrioR; + } + }; + } + + bool SpriteDeviceHelper::showBuffer( bool bIsVisible, SAL_UNUSED_PARAMETER bool /*bUpdateAll*/ ) + { + // hidden or disposed? + if( !bIsVisible || !mxContext->isInitialized() || !mpSpriteCanvas ) + return false; + + mxContext->makeCurrent(); + + SystemChildWindow* pChildWindow = mxContext->getChildWindow(); + const ::Size& rOutputSize = pChildWindow->GetSizePixel(); + initTransformation(rOutputSize); + + // render the actual spritecanvas content + mpSpriteCanvas->renderRecordedActions(); + + // render all sprites (in order of priority) on top of that + std::vector< ::rtl::Reference<CanvasCustomSprite> > aSprites( + maActiveSprites.begin(), + maActiveSprites.end()); + std::sort(aSprites.begin(), + aSprites.end(), + SpriteComparator()); + for( const auto& rSprite : aSprites ) + rSprite->renderSprite(); + + + // frame counter, other info + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + glTranslated(-1.0, 1.0, 0.0); + glScaled( 2.0 / rOutputSize.Width(), + -2.0 / rOutputSize.Height(), + 1.0 ); + + const double denominator( maLastUpdate.getElapsedTime() ); + maLastUpdate.reset(); + + const double fps(denominator == 0.0 ? 100.0 : 1.0/denominator); + std::vector<double> aVec { fps, static_cast<double>(maActiveSprites.size()), + static_cast<double>(mpTextureCache->getCacheSize()), + static_cast<double>(mpTextureCache->getCacheMissCount()), + static_cast<double>(mpTextureCache->getCacheHitCount()) }; + renderOSD( aVec, 20 ); + + /* + * TODO: moggi: fix it! + // switch buffer, sync etc. + const unx::Window aXWindow=pChildWindow->GetSystemData()->aWindow; + unx::glXSwapBuffers(reinterpret_cast<unx::Display*>(mpDisplay), + aXWindow); + pChildWindow->Show(); + unx::glXWaitGL(); + XSync( reinterpret_cast<unx::Display*>(mpDisplay), false ); + */ + mxContext->swapBuffers(); + + // flush texture cache, such that it does not build up + // indefinitely. + // TODO: have max cache size/LRU time in config, prune only on + // demand + mpTextureCache->prune(); + + return true; + } + + bool SpriteDeviceHelper::switchBuffer( bool bIsVisible, bool bUpdateAll ) + { + // no difference for VCL canvas + return showBuffer( bIsVisible, bUpdateAll ); + } + + uno::Any SpriteDeviceHelper::isAccelerated() const + { + return css::uno::Any(false); + } + + uno::Any SpriteDeviceHelper::getDeviceHandle() const + { + const SystemChildWindow* pChildWindow = mxContext->getChildWindow(); + const OutputDevice* pDevice = pChildWindow ? pChildWindow->GetOutDev() : nullptr; + return uno::Any(reinterpret_cast<sal_Int64>(pDevice)); + } + + uno::Any SpriteDeviceHelper::getSurfaceHandle() const + { + return uno::Any(); + } + + uno::Reference<rendering::XColorSpace> SpriteDeviceHelper::getColorSpace() const + { + // always the same + return ::canvas::tools::getStdColorSpace(); + } + + void SpriteDeviceHelper::notifySizeUpdate( const awt::Rectangle& rBounds ) + { + if( mxContext->isInitialized() ) + { + SystemChildWindow* pChildWindow = mxContext->getChildWindow(); + pChildWindow->setPosSizePixel( + 0,0,rBounds.Width,rBounds.Height); + } + } + + void SpriteDeviceHelper::dumpScreenContent() const + { + SAL_INFO("canvas.ogl", __func__ ); + } + + void SpriteDeviceHelper::show( const ::rtl::Reference< CanvasCustomSprite >& xSprite ) + { + maActiveSprites.insert(xSprite); + } + + void SpriteDeviceHelper::hide( const ::rtl::Reference< CanvasCustomSprite >& xSprite ) + { + maActiveSprites.erase(xSprite); + } + + static void setupUniforms( unsigned int nProgramId, + const ::basegfx::B2DHomMatrix& rTexTransform ) + { + const GLint nTransformLocation = glGetUniformLocation(nProgramId, + "m_transform" ); + // OGL is column-major + float aTexTransform[] = + { + float(rTexTransform.get(0,0)), float(rTexTransform.get(1,0)), + float(rTexTransform.get(0,1)), float(rTexTransform.get(1,1)), + float(rTexTransform.get(0,2)), float(rTexTransform.get(1,2)) + }; + glUniformMatrix3x2fv(nTransformLocation,1,false,aTexTransform); + } + + static void setupUniforms( unsigned int nProgramId, + const rendering::ARGBColor* pColors, + const uno::Sequence< double >& rStops, + const ::basegfx::B2DHomMatrix& rTexTransform ) + { + glUseProgram(nProgramId); + + GLuint nColorsTexture; + glActiveTexture(GL_TEXTURE0); + glGenTextures(1, &nColorsTexture); + glBindTexture(GL_TEXTURE_1D, nColorsTexture); + + const sal_Int32 nColors=rStops.getLength(); + glTexImage1D( GL_TEXTURE_1D, 0, GL_RGBA, nColors, 0, GL_RGBA, GL_DOUBLE, pColors ); + glTexParameteri( GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_NEAREST ); + glTexParameteri( GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_NEAREST ); + + GLuint nStopsTexture; + glActiveTexture(GL_TEXTURE1); + glGenTextures(1, &nStopsTexture); + glBindTexture(GL_TEXTURE_1D, nStopsTexture); + + glTexImage1D( GL_TEXTURE_1D, 0, GL_ALPHA, nColors, 0, GL_ALPHA, GL_DOUBLE, rStops.getConstArray() ); + glTexParameteri( GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_NEAREST ); + glTexParameteri( GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_NEAREST ); + + const GLint nColorArrayLocation = glGetUniformLocation(nProgramId, + "t_colorArray4d" ); + glUniform1i( nColorArrayLocation, 0 ); // unit 0 + + const GLint nStopArrayLocation = glGetUniformLocation(nProgramId, + "t_stopArray1d" ); + glUniform1i( nStopArrayLocation, 1 ); // unit 1 + + const GLint nNumColorLocation = glGetUniformLocation(nProgramId, + "i_nColors" ); + glUniform1i( nNumColorLocation, nColors-1 ); + + setupUniforms(nProgramId,rTexTransform); + } + + static void setupUniforms( unsigned int nProgramId, + const rendering::ARGBColor& rStartColor, + const rendering::ARGBColor& rEndColor, + const ::basegfx::B2DHomMatrix& rTexTransform ) + { + glUseProgram(nProgramId); + + const GLint nStartColorLocation = glGetUniformLocation(nProgramId, + "v_startColor4d" ); + glUniform4f(nStartColorLocation, + rStartColor.Red, + rStartColor.Green, + rStartColor.Blue, + rStartColor.Alpha); + + const GLint nEndColorLocation = glGetUniformLocation(nProgramId, + "v_endColor4d" ); + glUniform4f(nEndColorLocation, + rEndColor.Red, + rEndColor.Green, + rEndColor.Blue, + rEndColor.Alpha); + + setupUniforms(nProgramId,rTexTransform); + } + + void SpriteDeviceHelper::useLinearGradientShader( const rendering::ARGBColor* pColors, + const uno::Sequence< double >& rStops, + const ::basegfx::B2DHomMatrix& rTexTransform ) + { + if( rStops.getLength() > 2 ) + setupUniforms(mnLinearMultiColorGradientProgram, pColors, rStops, rTexTransform); + else + setupUniforms(mnLinearTwoColorGradientProgram, pColors[0], pColors[1], rTexTransform); + } + + void SpriteDeviceHelper::useRadialGradientShader( const rendering::ARGBColor* pColors, + const uno::Sequence< double >& rStops, + const ::basegfx::B2DHomMatrix& rTexTransform ) + { + if( rStops.getLength() > 2 ) + setupUniforms(mnRadialMultiColorGradientProgram, pColors, rStops, rTexTransform); + else + setupUniforms(mnRadialTwoColorGradientProgram, pColors[0], pColors[1], rTexTransform); + } + + void SpriteDeviceHelper::useRectangularGradientShader( const rendering::ARGBColor* pColors, + const uno::Sequence< double >& rStops, + const ::basegfx::B2DHomMatrix& rTexTransform ) + { + if( rStops.getLength() > 2 ) + setupUniforms(mnRectangularMultiColorGradientProgram, pColors, rStops, rTexTransform); + else + setupUniforms(mnRectangularTwoColorGradientProgram, pColors[0], pColors[1], rTexTransform); + } + + namespace + { + + class BufferContextImpl : public IBufferContext + { + GLuint mnFramebufferId; + GLuint mnDepthId; + GLuint mnTextureId; + + virtual void startBufferRendering() override + { + glBindFramebuffer(GL_FRAMEBUFFER, mnFramebufferId); + } + + virtual void endBufferRendering() override + { + glBindFramebuffer(GL_FRAMEBUFFER, 0); + } + + virtual GLuint getTextureId() override + { + return mnTextureId; + } + + public: + explicit BufferContextImpl(const ::basegfx::B2IVector& rSize) : + mnFramebufferId(0), + mnDepthId(0), + mnTextureId(0) + { + OpenGLHelper::createFramebuffer(rSize.getX(), rSize.getY(), mnFramebufferId, + mnDepthId, mnTextureId); + } + + virtual ~BufferContextImpl() override + { + glDeleteTextures(1, &mnTextureId); + glDeleteRenderbuffers(1, &mnDepthId); + glDeleteFramebuffers(1, &mnFramebufferId); + } + }; + } + + IBufferContextSharedPtr SpriteDeviceHelper::createBufferContext(const ::basegfx::B2IVector& rSize) const + { + return std::make_shared<BufferContextImpl>(rSize); + } + + TextureCache& SpriteDeviceHelper::getTextureCache() const + { + return *mpTextureCache; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/opengl/ogl_spritedevicehelper.hxx b/canvas/source/opengl/ogl_spritedevicehelper.hxx new file mode 100644 index 0000000000..3b9a262625 --- /dev/null +++ b/canvas/source/opengl/ogl_spritedevicehelper.hxx @@ -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/. + */ + +#pragma once + +#include <vcl/opengl/OpenGLContext.hxx> + +#include <rtl/ref.hxx> +#include <canvas/elapsedtime.hxx> +#include <com/sun/star/rendering/XGraphicDevice.hpp> + +#include "ogl_buffercontext.hxx" + +#include <set> + +namespace vcl { class Window; } +class SystemChildWindow; +namespace basegfx{ class B2IVector; class B2DHomMatrix; } +namespace com::sun::star::awt { struct Rectangle; } +namespace com::sun::star::geometry { struct AffineMatrix2D; } + + +namespace oglcanvas +{ + class TextureCache; + class SpriteCanvas; + class CanvasCustomSprite; + class CanvasHelper; + + class SpriteDeviceHelper + { + public: + SpriteDeviceHelper(); + ~SpriteDeviceHelper(); + + /// make noncopyable + SpriteDeviceHelper(const SpriteDeviceHelper&) = delete; + const SpriteDeviceHelper& operator=(const SpriteDeviceHelper&) = delete; + + void init( vcl::Window& rWindow, + SpriteCanvas& rSpriteCanvas, + const css::awt::Rectangle& rViewArea ); + + /// Dispose all internal references + void disposing(); + + // XWindowGraphicDevice + css::geometry::RealSize2D getPhysicalResolution(); + css::geometry::RealSize2D getPhysicalSize(); + css::uno::Reference< css::rendering::XLinePolyPolygon2D > createCompatibleLinePolyPolygon( + const css::uno::Reference< css::rendering::XGraphicDevice >& rDevice, + const css::uno::Sequence< css::uno::Sequence< css::geometry::RealPoint2D > >& points ); + css::uno::Reference< css::rendering::XBezierPolyPolygon2D > createCompatibleBezierPolyPolygon( + const css::uno::Reference< css::rendering::XGraphicDevice >& rDevice, + const css::uno::Sequence< css::uno::Sequence< css::geometry::RealBezierSegment2D > >& points ); + css::uno::Reference< css::rendering::XBitmap > createCompatibleBitmap( + const css::uno::Reference< css::rendering::XGraphicDevice >& rDevice, + const css::geometry::IntegerSize2D& size ); + css::uno::Reference< css::rendering::XVolatileBitmap > createVolatileBitmap( + const css::uno::Reference< css::rendering::XGraphicDevice >& rDevice, + const css::geometry::IntegerSize2D& size ); + css::uno::Reference< css::rendering::XBitmap > createCompatibleAlphaBitmap( + const css::uno::Reference< css::rendering::XGraphicDevice >& rDevice, + const css::geometry::IntegerSize2D& size ); + css::uno::Reference< css::rendering::XVolatileBitmap > createVolatileAlphaBitmap( + const css::uno::Reference< css::rendering::XGraphicDevice >& rDevice, + const css::geometry::IntegerSize2D& size ); + + bool showBuffer( bool bIsVisible, bool bUpdateAll ); + bool switchBuffer( bool bIsVisible, bool bUpdateAll ); + + css::uno::Any isAccelerated() const; + css::uno::Any getDeviceHandle() const; + css::uno::Any getSurfaceHandle() const; + css::uno::Reference< + css::rendering::XColorSpace > getColorSpace() const; + + void notifySizeUpdate( const css::awt::Rectangle& rBounds ); + + /** called when DumpScreenContent property is enabled on + XGraphicDevice, and writes out bitmaps of current screen. + */ + void dumpScreenContent() const; + + void show( const ::rtl::Reference< CanvasCustomSprite >& ); + void hide( const ::rtl::Reference< CanvasCustomSprite >& ); + + /// enable linear gradient shader "texture" with given parameters + void useLinearGradientShader( const css::rendering::ARGBColor* pColors, + const css::uno::Sequence< double >& rStops, + const ::basegfx::B2DHomMatrix& rTexTransform ); + /// enable radial gradient shader "texture" with given parameters + void useRadialGradientShader( const css::rendering::ARGBColor* pColors, + const css::uno::Sequence< double >& rStops, + const ::basegfx::B2DHomMatrix& rTexTransform ); + /// enable rectangular gradient shader "texture" with given parameters + void useRectangularGradientShader( const css::rendering::ARGBColor* pColors, + const css::uno::Sequence< double >& rStops, + const ::basegfx::B2DHomMatrix& rTexTransform ); + + /// create a pbuffer context (for rendering into background surface) + IBufferContextSharedPtr createBufferContext(const ::basegfx::B2IVector& rSize) const; + + /// Get instance of internal texture cache + TextureCache& getTextureCache() const; + + private: + /// Pointer to sprite canvas (owner of this helper), needed to create bitmaps + SpriteCanvas* mpSpriteCanvas; + + std::set< ::rtl::Reference< CanvasCustomSprite > > maActiveSprites; + + /// For the frame counter timings + ::canvas::tools::ElapsedTime maLastUpdate; + + std::shared_ptr<TextureCache> mpTextureCache; + + unsigned int mnLinearTwoColorGradientProgram; + unsigned int mnLinearMultiColorGradientProgram; + unsigned int mnRadialTwoColorGradientProgram; + unsigned int mnRadialMultiColorGradientProgram; + unsigned int mnRectangularTwoColorGradientProgram; + unsigned int mnRectangularMultiColorGradientProgram; + + rtl::Reference<OpenGLContext> mxContext; + }; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/opengl/ogl_textlayout.cxx b/canvas/source/opengl/ogl_textlayout.cxx new file mode 100644 index 0000000000..a873652754 --- /dev/null +++ b/canvas/source/opengl/ogl_textlayout.cxx @@ -0,0 +1,195 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include <sal/config.h> +#include <sal/log.hxx> +#include <utility> + +#include <comphelper/diagnose_ex.hxx> + +#include "ogl_textlayout.hxx" + +using namespace ::com::sun::star; + +namespace oglcanvas +{ + TextLayout::TextLayout( rendering::StringContext aText, + sal_Int8 nDirection, + sal_Int64 /*nRandomSeed*/, + CanvasFont::ImplRef rFont ) : + maText(std::move( aText )), + mpFont(std::move( rFont )), + mnTextDirection( nDirection ) + { + } + + void TextLayout::disposing(std::unique_lock<std::mutex>& /*rGuard*/) + { + mpFont.clear(); + } + + // XTextLayout + uno::Sequence< uno::Reference< rendering::XPolyPolygon2D > > SAL_CALL TextLayout::queryTextShapes( ) + { + // TODO + return uno::Sequence< uno::Reference< rendering::XPolyPolygon2D > >(); + } + + uno::Sequence< geometry::RealRectangle2D > SAL_CALL TextLayout::queryInkMeasures( ) + { + // TODO + return uno::Sequence< geometry::RealRectangle2D >(); + } + + uno::Sequence< geometry::RealRectangle2D > SAL_CALL TextLayout::queryMeasures( ) + { + // TODO + return uno::Sequence< geometry::RealRectangle2D >(); + } + + uno::Sequence< double > SAL_CALL TextLayout::queryLogicalAdvancements( ) + { + std::unique_lock aGuard( m_aMutex ); + + return maLogicalAdvancements; + } + + void SAL_CALL TextLayout::applyLogicalAdvancements( const uno::Sequence< double >& aAdvancements ) + { + std::unique_lock aGuard( m_aMutex ); + + if( aAdvancements.getLength() != maText.Length ) + { + SAL_INFO("canvas.ogl", "TextLayout::applyLogicalAdvancements(): mismatching number of advancements"); + throw lang::IllegalArgumentException(); + } + + maLogicalAdvancements = aAdvancements; + } + + uno::Sequence< sal_Bool > SAL_CALL TextLayout::queryKashidaPositions( ) + { + std::unique_lock aGuard( m_aMutex ); + + return maKashidaPositions; + } + + void SAL_CALL TextLayout::applyKashidaPositions( const uno::Sequence< sal_Bool >& aPositions ) + { + std::unique_lock aGuard( m_aMutex ); + + if( aPositions.hasElements() && aPositions.getLength() != maText.Length ) + { + SAL_WARN("canvas.ogl", "TextLayout::applyKashidaPositions(): mismatching number of positions" ); + throw lang::IllegalArgumentException("mismatching number of positions", getXWeak(), 1); + } + + maKashidaPositions = aPositions; + } + + geometry::RealRectangle2D SAL_CALL TextLayout::queryTextBounds( ) + { + std::unique_lock aGuard( m_aMutex ); + + ENSURE_OR_THROW( mpFont, + "TextLayout::queryTextBounds(): invalid font" ); + + // fake text bounds by either taking the advancement values, + // or assuming square glyph boxes (width similar to height) + const rendering::FontRequest& rFontRequest( mpFont->getFontRequest() ); + const double nFontSize( std::max( rFontRequest.CellSize, + rFontRequest.ReferenceAdvancement ) ); + if( maLogicalAdvancements.hasElements() ) + { + return geometry::RealRectangle2D( 0, -nFontSize/2, + maLogicalAdvancements[ maLogicalAdvancements.getLength()-1 ], + nFontSize/2 ); + } + else + { + return geometry::RealRectangle2D( 0, -nFontSize/2, + nFontSize * maText.Length, + nFontSize/2 ); + } + } + + double SAL_CALL TextLayout::justify( double /*nSize*/ ) + { + // TODO + return 0.0; + } + + double SAL_CALL TextLayout::combinedJustify( const uno::Sequence< uno::Reference< rendering::XTextLayout > >& /*aNextLayouts*/, + double /*nSize*/ ) + { + // TODO + return 0.0; + } + + rendering::TextHit SAL_CALL TextLayout::getTextHit( const geometry::RealPoint2D& /*aHitPoint*/ ) + { + // TODO + return rendering::TextHit(); + } + + rendering::Caret SAL_CALL TextLayout::getCaret( sal_Int32 /*nInsertionIndex*/, + sal_Bool /*bExcludeLigatures*/ ) + { + // TODO + return rendering::Caret(); + } + + sal_Int32 SAL_CALL TextLayout::getNextInsertionIndex( sal_Int32 /*nStartIndex*/, + sal_Int32 /*nCaretAdvancement*/, + sal_Bool /*bExcludeLigatures*/ ) + { + // TODO + return 0; + } + + uno::Reference< rendering::XPolyPolygon2D > SAL_CALL TextLayout::queryVisualHighlighting( sal_Int32 /*nStartIndex*/, + sal_Int32 /*nEndIndex*/ ) + { + // TODO + return uno::Reference< rendering::XPolyPolygon2D >(); + } + + uno::Reference< rendering::XPolyPolygon2D > SAL_CALL TextLayout::queryLogicalHighlighting( sal_Int32 /*nStartIndex*/, + sal_Int32 /*nEndIndex*/ ) + { + // TODO + return uno::Reference< rendering::XPolyPolygon2D >(); + } + + double SAL_CALL TextLayout::getBaselineOffset( ) + { + // TODO + return 0.0; + } + + sal_Int8 SAL_CALL TextLayout::getMainTextDirection( ) + { + return mnTextDirection; + } + + uno::Reference< rendering::XCanvasFont > SAL_CALL TextLayout::getFont( ) + { + std::unique_lock aGuard( m_aMutex ); + + return mpFont; + } + + rendering::StringContext SAL_CALL TextLayout::getText( ) + { + return maText; + } + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/opengl/ogl_textlayout.hxx b/canvas/source/opengl/ogl_textlayout.hxx new file mode 100644 index 0000000000..d913ec3fb3 --- /dev/null +++ b/canvas/source/opengl/ogl_textlayout.hxx @@ -0,0 +1,71 @@ +/* -*- 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/. + */ + +#pragma once + +#include <comphelper/compbase.hxx> + +#include <com/sun/star/rendering/XTextLayout.hpp> + +#include "ogl_canvasfont.hxx" + + +/* Definition of TextLayout class */ + +namespace oglcanvas +{ + typedef ::comphelper::WeakComponentImplHelper< css::rendering::XTextLayout > TextLayoutBaseT; + + class TextLayout : public TextLayoutBaseT + { + public: + TextLayout( css::rendering::StringContext aText, + sal_Int8 nDirection, + sal_Int64 nRandomSeed, + CanvasFont::ImplRef rFont ); + + /// make noncopyable + TextLayout(const TextLayout&) = delete; + const TextLayout& operator=(const TextLayout&) = delete; + + /// Dispose all internal references + virtual void disposing(std::unique_lock<std::mutex>& rGuard) override; + + // XTextLayout + virtual css::uno::Sequence< css::uno::Reference< css::rendering::XPolyPolygon2D > > SAL_CALL queryTextShapes( ) override; + virtual css::uno::Sequence< css::geometry::RealRectangle2D > SAL_CALL queryInkMeasures( ) override; + virtual css::uno::Sequence< css::geometry::RealRectangle2D > SAL_CALL queryMeasures( ) override; + virtual css::uno::Sequence< double > SAL_CALL queryLogicalAdvancements( ) override; + virtual void SAL_CALL applyLogicalAdvancements( const css::uno::Sequence< double >& aAdvancements ) override; + virtual css::uno::Sequence< sal_Bool > SAL_CALL queryKashidaPositions( ) override; + virtual void SAL_CALL applyKashidaPositions( const css::uno::Sequence< sal_Bool >& aPositions ) override; + virtual css::geometry::RealRectangle2D SAL_CALL queryTextBounds( ) override; + virtual double SAL_CALL justify( double nSize ) override; + virtual double SAL_CALL combinedJustify( const css::uno::Sequence< css::uno::Reference< css::rendering::XTextLayout > >& aNextLayouts, double nSize ) override; + virtual css::rendering::TextHit SAL_CALL getTextHit( const css::geometry::RealPoint2D& aHitPoint ) override; + virtual css::rendering::Caret SAL_CALL getCaret( sal_Int32 nInsertionIndex, sal_Bool bExcludeLigatures ) override; + virtual sal_Int32 SAL_CALL getNextInsertionIndex( sal_Int32 nStartIndex, sal_Int32 nCaretAdvancement, sal_Bool bExcludeLigatures ) override; + virtual css::uno::Reference< css::rendering::XPolyPolygon2D > SAL_CALL queryVisualHighlighting( sal_Int32 nStartIndex, sal_Int32 nEndIndex ) override; + virtual css::uno::Reference< css::rendering::XPolyPolygon2D > SAL_CALL queryLogicalHighlighting( sal_Int32 nStartIndex, sal_Int32 nEndIndex ) override; + virtual double SAL_CALL getBaselineOffset( ) override; + virtual sal_Int8 SAL_CALL getMainTextDirection( ) override; + virtual css::uno::Reference< css::rendering::XCanvasFont > SAL_CALL getFont( ) override; + virtual css::rendering::StringContext SAL_CALL getText( ) override; + + private: + css::rendering::StringContext maText; + css::uno::Sequence< double > maLogicalAdvancements; + css::uno::Sequence< sal_Bool > maKashidaPositions; + CanvasFont::ImplRef mpFont; + sal_Int8 mnTextDirection; + }; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/opengl/ogl_texturecache.cxx b/canvas/source/opengl/ogl_texturecache.cxx new file mode 100644 index 0000000000..43fb7d8e2e --- /dev/null +++ b/canvas/source/opengl/ogl_texturecache.cxx @@ -0,0 +1,117 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include <sal/config.h> + +#include <epoxy/gl.h> + +#include <com/sun/star/geometry/IntegerSize2D.hpp> + +#include "ogl_texturecache.hxx" + + +using namespace ::com::sun::star; + +namespace oglcanvas +{ + TextureCache::TextureCache() : + maCache(101), + mnMissCount(0), + mnHitCount(0) + {} + + TextureCache::~TextureCache() + { + flush(); + } + + void TextureCache::flush() + { + // un-bind any texture + glBindTexture(GL_TEXTURE_2D, 0); + + // delete all cached textures + for( const auto& rCache : maCache ) + { + glDeleteTextures( 1, &rCache.second.nTexture ); + } + + maCache.clear(); + mnMissCount = 0; + mnHitCount = 0; + } + + void TextureCache::prune() + { + // un-bind any texture + glBindTexture(GL_TEXTURE_2D, 0); + + // delete already "old" textures, mark "new" entries "old" + const TextureCacheMapT::const_iterator aEnd = maCache.end(); + for( auto aCurr = maCache.begin(); aCurr != aEnd; /* increment managed in loop */) + { + if( aCurr->second.bOld ) + { + glDeleteTextures( 1, &aCurr->second.nTexture ); + aCurr = maCache.erase( aCurr ); + } + else + { + aCurr->second.bOld = true; + ++aCurr; + } + } + + mnMissCount = 0; + mnHitCount = 0; + } + + unsigned int TextureCache::getTexture( const geometry::IntegerSize2D& rPixelSize, + const sal_Int8* pPixel, + sal_uInt32 nPixelCrc32) const + { + unsigned int nTexture(0); + + // texture already cached? + TextureCacheMapT::iterator aCacheEntry; + if( (aCacheEntry=maCache.find(nPixelCrc32)) == maCache.end() ) + { + // nope, insert new entry + glGenTextures(1, &nTexture); + glBindTexture(GL_TEXTURE_2D, nTexture); + + // TODO(E3): handle limited texture sizes - + // glGetIntegerv(GL_MAX_TEXTURE_SIZE) + glTexImage2D(GL_TEXTURE_2D, + 0, + 4, + rPixelSize.Width, + rPixelSize.Height, + 0, + GL_RGBA, + GL_UNSIGNED_INT_8_8_8_8_REV, + pPixel); + + maCache[nPixelCrc32].nTexture = nTexture; + ++mnMissCount; + + return nTexture; + } + else + { + nTexture = aCacheEntry->second.nTexture; + aCacheEntry->second.bOld = false; + ++mnHitCount; + } + + return nTexture; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/opengl/ogl_texturecache.hxx b/canvas/source/opengl/ogl_texturecache.hxx new file mode 100644 index 0000000000..8d0a425ced --- /dev/null +++ b/canvas/source/opengl/ogl_texturecache.hxx @@ -0,0 +1,60 @@ +/* -*- 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/. + */ + +#pragma once + +#include <sal/types.h> +#include <unordered_map> + +namespace com::sun::star::geometry { struct IntegerSize2D; } + + +namespace oglcanvas +{ + class TextureCache + { + public: + TextureCache(); + ~TextureCache(); + + /// clear whole cache, reset statistic counters + void flush(); + + /** prune old entries from cache + + Every time this method is called, all cache entries are set + to "old". If subsequently not used by getTexture(), + they'll be entitled for expunge on the next prune() + call. Resets statistic counters. + */ + void prune(); + + /// Statistics + size_t getCacheSize() const { return maCache.size(); }; + sal_uInt32 getCacheMissCount() const { return mnMissCount; } + sal_uInt32 getCacheHitCount() const { return mnHitCount; } + + unsigned int getTexture( const css::geometry::IntegerSize2D& rPixelSize, + const sal_Int8* pPixel, + sal_uInt32 nPixelCrc32) const; + private: + struct CacheEntry + { + CacheEntry() : nTexture(0), bOld(false) {} + unsigned int nTexture; + bool bOld; + }; + typedef std::unordered_map<sal_uInt32,CacheEntry> TextureCacheMapT; + mutable TextureCacheMapT maCache; + mutable sal_uInt32 mnMissCount; + mutable sal_uInt32 mnHitCount; + }; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/opengl/ogl_tools.hxx b/canvas/source/opengl/ogl_tools.hxx new file mode 100644 index 0000000000..8f42f734e6 --- /dev/null +++ b/canvas/source/opengl/ogl_tools.hxx @@ -0,0 +1,27 @@ +/* -*- 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/. + */ + +#pragma once + +#include <sal/config.h> +#include <epoxy/gl.h> + +namespace oglcanvas +{ + struct TransformationPreserver + { + TransformationPreserver() + { glPushMatrix(); } + + ~TransformationPreserver() + { glPopMatrix(); } + }; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/opengl/oglcanvas.component b/canvas/source/opengl/oglcanvas.component new file mode 100644 index 0000000000..f6c9af43fd --- /dev/null +++ b/canvas/source/opengl/oglcanvas.component @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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/. + * +--> + +<component loader="com.sun.star.loader.SharedLibrary" environment="@CPPU_ENV@" + xmlns="http://openoffice.org/2010/uno-components"> + <implementation name="com.sun.star.comp.rendering.SpriteCanvas.OGL" + constructor="com_sun_star_comp_rendering_SpriteCanvas_OGL_get_implementation"> + <service name="com.sun.star.rendering.SpriteCanvas.OGL"/> + </implementation> +</component> diff --git a/canvas/source/simplecanvas/simplecanvas.component b/canvas/source/simplecanvas/simplecanvas.component new file mode 100644 index 0000000000..9be11ad479 --- /dev/null +++ b/canvas/source/simplecanvas/simplecanvas.component @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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 . + --> + +<component loader="com.sun.star.loader.SharedLibrary" environment="@CPPU_ENV@" + xmlns="http://openoffice.org/2010/uno-components"> + <implementation name="com.sun.star.comp.rendering.SimpleCanvas" + constructor="com_sun_star_comp_rendering_SimpleCanvas"> + <service name="com.sun.star.rendering.SimpleCanvas"/> + </implementation> +</component> diff --git a/canvas/source/simplecanvas/simplecanvasimpl.cxx b/canvas/source/simplecanvas/simplecanvasimpl.cxx new file mode 100644 index 0000000000..db1377e419 --- /dev/null +++ b/canvas/source/simplecanvas/simplecanvasimpl.cxx @@ -0,0 +1,366 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <com/sun/star/lang/IllegalArgumentException.hpp> +#include <com/sun/star/lang/XServiceName.hpp> +#include <com/sun/star/rendering/CompositeOperation.hpp> +#include <com/sun/star/rendering/PanoseLetterForm.hpp> +#include <com/sun/star/rendering/PanoseWeight.hpp> +#include <com/sun/star/rendering/XSimpleCanvas.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <comphelper/compbase.hxx> +#include <o3tl/lazy_update.hxx> + +#include <canvas/canvastools.hxx> + + +#include <functional> + +using namespace ::com::sun::star; +using namespace canvas; + +namespace +{ + uno::Sequence< double > color2Sequence( sal_Int32 nColor ) + { + // TODO(F3): Color management + uno::Sequence< double > aRes{ + static_cast<sal_uInt8>( (nColor&0xFF000000U) >> 24U ) / 255.0, + static_cast<sal_uInt8>( (nColor&0x00FF0000U) >> 16U ) / 255.0, + static_cast<sal_uInt8>( (nColor&0x0000FF00U) >> 8U ) / 255.0, + static_cast<sal_uInt8>( (nColor&0x000000FFU) ) / 255.0 + }; + return aRes; + } + + uno::Reference< rendering::XPolyPolygon2D > rect2Poly( uno::Reference<rendering::XGraphicDevice> const& xDevice, + geometry::RealRectangle2D const& rRect ) + { + uno::Sequence< geometry::RealPoint2D > rectSequence{ + geometry::RealPoint2D( rRect.X1, rRect.Y1 ), + geometry::RealPoint2D( rRect.X2, rRect.Y1 ), + geometry::RealPoint2D( rRect.X2, rRect.Y2 ), + geometry::RealPoint2D( rRect.X1, rRect.Y2 ) + }; + uno::Sequence< uno::Sequence< geometry::RealPoint2D > > sequenceSequence{ rectSequence }; + uno::Reference< rendering::XPolyPolygon2D > xRes = + xDevice->createCompatibleLinePolyPolygon( sequenceSequence ); + if( xRes.is() ) + xRes->setClosed( 0, true ); + return xRes; + } + + struct SimpleRenderState + { + o3tl::LazyUpdate<sal_Int32, + uno::Sequence<double>, + decltype(&color2Sequence)> m_aPenColor; + o3tl::LazyUpdate<sal_Int32, + uno::Sequence<double>, + decltype(&color2Sequence)> m_aFillColor; + o3tl::LazyUpdate<geometry::RealRectangle2D, + uno::Reference< rendering::XPolyPolygon2D >, + std::function<uno::Reference<rendering::XPolyPolygon2D> (geometry::RealRectangle2D)> > m_aRectClip; + geometry::AffineMatrix2D m_aTransformation; + + explicit SimpleRenderState( uno::Reference<rendering::XGraphicDevice> const& xDevice ) : + m_aPenColor( &color2Sequence), + m_aFillColor( &color2Sequence ), + m_aRectClip( [&xDevice](geometry::RealRectangle2D const& rRect) { return rect2Poly(xDevice, rRect); } ) + { + tools::setIdentityAffineMatrix2D( m_aTransformation ); + } + }; + + + typedef ::comphelper::WeakComponentImplHelper< css::rendering::XSimpleCanvas, + css::lang::XServiceName > SimpleCanvasBase; + + class SimpleCanvasImpl : public SimpleCanvasBase + { + private: + bool isStrokingEnabled() const + { + return maRenderState.m_aPenColor.getInValue() % 0x100 != 0; + } + + rendering::RenderState createStrokingRenderState() const + { + return rendering::RenderState(maRenderState.m_aTransformation, + *maRenderState.m_aRectClip, + *maRenderState.m_aPenColor, + rendering::CompositeOperation::OVER); + } + + bool isFillingEnabled() const + { + return maRenderState.m_aFillColor.getInValue() % 0x100 != 0; + } + + rendering::RenderState createFillingRenderState() const + { + return rendering::RenderState(maRenderState.m_aTransformation, + *maRenderState.m_aRectClip, + *maRenderState.m_aFillColor, + rendering::CompositeOperation::OVER); + } + + static uno::Reference<rendering::XCanvas> grabCanvas( uno::Sequence<uno::Any> const& rArgs ) + { + uno::Reference<rendering::XCanvas> xRet; + + // can't do much without an XCanvas, can't we? + if( !rArgs.hasElements() ) + throw lang::IllegalArgumentException(); + + xRet.set( rArgs[0], uno::UNO_QUERY ); + + // can't do much without an XCanvas, can't we? + if( !xRet.is() ) + throw lang::IllegalArgumentException(); + + return xRet; + } + + public: + SimpleCanvasImpl( const uno::Sequence< uno::Any >& aArguments, + const uno::Reference< uno::XComponentContext >& ) : + mxCanvas( grabCanvas(aArguments) ), + maFont([this](rendering::FontRequest const& rFontRequest) { + return mxCanvas->createFont(rFontRequest, + uno::Sequence< beans::PropertyValue >(), + geometry::Matrix2D()); } ), + maRenderState( mxCanvas->getDevice() ) + { + tools::initViewState(maViewState); + } + + + private: + // Ifc XServiceName + virtual OUString SAL_CALL getServiceName( ) override + { + return "com.sun.star.rendering.SimpleCanvas"; + } + + // Ifc XSimpleCanvas + virtual void SAL_CALL selectFont( const OUString& sFontName, + double size, + sal_Bool bold, + sal_Bool italic ) override + { + std::unique_lock aGuard( m_aMutex ); + + maFont->FontDescription.FamilyName = sFontName; + maFont->CellSize = size; + maFont->FontDescription.FontDescription.Weight = + bold ? rendering::PanoseWeight::BOLD : rendering::PanoseWeight::MEDIUM; + maFont->FontDescription.FontDescription.Letterform = + italic ? rendering::PanoseLetterForm::OBLIQUE_CONTACT : rendering::PanoseLetterForm::ANYTHING; + } + + virtual void SAL_CALL setPenColor( ::sal_Int32 nsRgbaColor ) override + { + std::unique_lock aGuard( m_aMutex ); + *(maRenderState.m_aPenColor) = nsRgbaColor; + } + + virtual void SAL_CALL setFillColor( ::sal_Int32 nsRgbaColor ) override + { + std::unique_lock aGuard( m_aMutex ); + *(maRenderState.m_aFillColor) = nsRgbaColor; + } + + virtual void SAL_CALL setRectClip( const geometry::RealRectangle2D& aRect ) override + { + std::unique_lock aGuard( m_aMutex ); + *(maRenderState.m_aRectClip) = aRect; + } + + virtual void SAL_CALL setTransformation( const geometry::AffineMatrix2D& aTransform ) override + { + std::unique_lock aGuard( m_aMutex ); + maRenderState.m_aTransformation = aTransform; + } + + virtual void SAL_CALL drawPixel( const geometry::RealPoint2D& aPoint ) override + { + std::unique_lock aGuard( m_aMutex ); + mxCanvas->drawPoint(aPoint, + maViewState, + createFillingRenderState()); + } + + virtual void SAL_CALL drawLine( const geometry::RealPoint2D& aStartPoint, + const geometry::RealPoint2D& aEndPoint ) override + { + std::unique_lock aGuard( m_aMutex ); + mxCanvas->drawLine(aStartPoint, + aEndPoint, + maViewState, + createStrokingRenderState()); + } + + virtual void SAL_CALL drawRect( const geometry::RealRectangle2D& aRect ) override + { + std::unique_lock aGuard( m_aMutex ); + uno::Reference< rendering::XPolyPolygon2D > xPoly( + rect2Poly( mxCanvas->getDevice(), + aRect)); + + if( isFillingEnabled() ) + mxCanvas->drawPolyPolygon(xPoly, + maViewState, + createFillingRenderState()); + if( isStrokingEnabled() ) + mxCanvas->drawPolyPolygon(xPoly, + maViewState, + createStrokingRenderState()); + } + + virtual void SAL_CALL drawPolyPolygon( const uno::Reference< rendering::XPolyPolygon2D >& xPolyPolygon ) override + { + std::unique_lock aGuard( m_aMutex ); + + if( isFillingEnabled() ) + mxCanvas->drawPolyPolygon(xPolyPolygon, + maViewState, + createFillingRenderState()); + if( isStrokingEnabled() ) + mxCanvas->drawPolyPolygon(xPolyPolygon, + maViewState, + createStrokingRenderState()); + } + + virtual void SAL_CALL drawText( const rendering::StringContext& aText, + const geometry::RealPoint2D& aOutPos, + ::sal_Int8 nTextDirection ) override + { + std::unique_lock aGuard( m_aMutex ); + const basegfx::B2DHomMatrix offsetTransform(basegfx::utils::createTranslateB2DHomMatrix(aOutPos.X,aOutPos.Y)); + rendering::RenderState aRenderState( createStrokingRenderState() ); + tools::appendToRenderState(aRenderState, offsetTransform); + + mxCanvas->drawText(aText, + maFont.getOutValue(), + maViewState, + aRenderState, + nTextDirection); + } + + virtual void SAL_CALL drawBitmap( const uno::Reference< rendering::XBitmap >& xBitmap, + const geometry::RealPoint2D& aLeftTop ) override + { + std::unique_lock aGuard( m_aMutex ); + const basegfx::B2DHomMatrix offsetTransform(basegfx::utils::createTranslateB2DHomMatrix(aLeftTop.X,aLeftTop.Y)); + rendering::RenderState aRenderState( createStrokingRenderState() ); + tools::appendToRenderState(aRenderState, offsetTransform); + + mxCanvas->drawBitmap(xBitmap,maViewState,aRenderState); + } + + virtual uno::Reference< rendering::XGraphicDevice > SAL_CALL getDevice( ) override + { + std::unique_lock aGuard( m_aMutex ); + return mxCanvas->getDevice(); + } + + virtual uno::Reference< rendering::XCanvas > SAL_CALL getCanvas( ) override + { + std::unique_lock aGuard( m_aMutex ); + return mxCanvas; + } + + virtual rendering::FontMetrics SAL_CALL getFontMetrics( ) override + { + std::unique_lock aGuard( m_aMutex ); + return maFont.getOutValue()->getFontMetrics(); + } + + virtual uno::Reference< rendering::XCanvasFont > SAL_CALL getCurrentFont( ) override + { + std::unique_lock aGuard( m_aMutex ); + return maFont.getOutValue(); + } + + virtual ::sal_Int32 SAL_CALL getCurrentPenColor( ) override + { + std::unique_lock aGuard( m_aMutex ); + return maRenderState.m_aPenColor.getInValue(); + } + + virtual ::sal_Int32 SAL_CALL getCurrentFillColor( ) override + { + std::unique_lock aGuard( m_aMutex ); + return maRenderState.m_aFillColor.getInValue(); + } + + virtual geometry::RealRectangle2D SAL_CALL getCurrentClipRect( ) override + { + std::unique_lock aGuard( m_aMutex ); + return maRenderState.m_aRectClip.getInValue(); + } + + virtual geometry::AffineMatrix2D SAL_CALL getCurrentTransformation( ) override + { + std::unique_lock aGuard( m_aMutex ); + return maRenderState.m_aTransformation; + } + + virtual rendering::ViewState SAL_CALL getCurrentViewState( ) override + { + std::unique_lock aGuard( m_aMutex ); + return maViewState; + } + + virtual rendering::RenderState SAL_CALL getCurrentRenderState( sal_Bool bUseFillColor ) override + { + std::unique_lock aGuard( m_aMutex ); + if( bUseFillColor ) + return createFillingRenderState(); + else + return createStrokingRenderState(); + } + + + typedef o3tl::LazyUpdate< + rendering::FontRequest, + uno::Reference< rendering::XCanvasFont >, + std::function<uno::Reference<rendering::XCanvasFont> (rendering::FontRequest)> > SimpleFont; + + uno::Reference<rendering::XCanvas> mxCanvas; + SimpleFont maFont; + rendering::ViewState maViewState; + SimpleRenderState maRenderState; + }; + +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +com_sun_star_comp_rendering_SimpleCanvas( + css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const& args) +{ + return cppu::acquire(new SimpleCanvasImpl(args, context)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/tools/cachedprimitivebase.cxx b/canvas/source/tools/cachedprimitivebase.cxx new file mode 100644 index 0000000000..7aaf3d58ac --- /dev/null +++ b/canvas/source/tools/cachedprimitivebase.cxx @@ -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 . + */ + +#include <sal/config.h> + +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/utils/canvastools.hxx> +#include <com/sun/star/rendering/RepaintResult.hpp> +#include <cppuhelper/supportsservice.hxx> + +#include <base/cachedprimitivebase.hxx> +#include <utility> + + +using namespace ::com::sun::star; + +namespace canvas +{ + CachedPrimitiveBase::CachedPrimitiveBase( rendering::ViewState aUsedViewState, + uno::Reference< rendering::XCanvas > xTarget ) : + maUsedViewState(std::move( aUsedViewState )), + mxTarget(std::move( xTarget )) + { + } + + CachedPrimitiveBase::~CachedPrimitiveBase() + { + } + + void CachedPrimitiveBase::disposing(std::unique_lock<std::mutex>& /*rGuard*/) + { + 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 0000000000..975d62325d --- /dev/null +++ b/canvas/source/tools/canvascustomspritehelper.cxx @@ -0,0 +1,439 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <com/sun/star/rendering/XPolyPolygon2D.hpp> +#include <com/sun/star/geometry/RealSize2D.hpp> +#include <com/sun/star/rendering/XBitmap.hpp> +#include <com/sun/star/geometry/IntegerSize2D.hpp> +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/point/b2dpoint.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/polygon/b2dpolypolygontools.hxx> +#include <basegfx/utils/canvastools.hxx> +#include <basegfx/vector/b2dsize.hxx> +#include <rtl/math.hxx> +#include <comphelper/diagnose_ex.hxx> + +#include <base/canvascustomspritehelper.hxx> +#include <canvas/canvastools.hxx> + +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() : + 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, + "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()); + basegfx::B2DSize rOurSize(rSprite->getSizePixel().getX(), rSprite->getSizePixel().getY()); + + ::basegfx::B2DHomMatrix aTransform; + if( tools::isInside( + ::basegfx::B2DRectangle( 0.0,0.0, + rOurSize.getWidth(), + rOurSize.getHeight() ), + ::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 0000000000..1ff3930057 --- /dev/null +++ b/canvas/source/tools/canvastools.cxx @@ -0,0 +1,1308 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <limits> + +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <basegfx/numeric/ftools.hxx> +#include <basegfx/point/b2dpoint.hxx> +#include <basegfx/point/b2ipoint.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/range/b2drange.hxx> +#include <basegfx/range/b2drectangle.hxx> +#include <basegfx/range/b2irange.hxx> +#include <basegfx/utils/canvastools.hxx> +#include <basegfx/vector/b2ivector.hxx> +#include <com/sun/star/awt/Rectangle.hpp> +#include <com/sun/star/awt/XWindow2.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/geometry/AffineMatrix2D.hpp> +#include <com/sun/star/geometry/Matrix2D.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/rendering/ColorComponentTag.hpp> +#include <com/sun/star/rendering/ColorSpaceType.hpp> +#include <com/sun/star/rendering/CompositeOperation.hpp> +#include <com/sun/star/rendering/IntegerBitmapLayout.hpp> +#include <com/sun/star/rendering/RenderState.hpp> +#include <com/sun/star/rendering/RenderingIntent.hpp> +#include <com/sun/star/rendering/ViewState.hpp> +#include <com/sun/star/rendering/XCanvas.hpp> +#include <com/sun/star/rendering/XColorSpace.hpp> +#include <com/sun/star/rendering/XIntegerBitmapColorSpace.hpp> +#include <com/sun/star/util/Endianness.hpp> +#include <cppuhelper/implbase.hxx> +#include <sal/log.hxx> +#include <toolkit/helper/vclunohelper.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <vcl/canvastools.hxx> +#include <vcl/window.hxx> + +#include <canvas/canvastools.hxx> + + +using namespace ::com::sun::star; + +namespace canvas::tools +{ + geometry::RealSize2D createInfiniteSize2D() + { + return geometry::RealSize2D( + std::numeric_limits<double>::infinity(), + std::numeric_limits<double>::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<rendering::ARGBColor> 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<rendering::XColorSpace*>(this), 0); + + uno::Sequence< rendering::RGBColor > aRes(nLen/4); + rendering::RGBColor* pOut( aRes.getArray() ); + for( std::size_t i=0; i<nLen; i+=4 ) + { + *pOut++ = rendering::RGBColor(pIn[0],pIn[1],pIn[2]); + pIn += 4; + } + return aRes; + } + virtual uno::Sequence< rendering::ARGBColor > 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<rendering::XColorSpace*>(this), 0); + + uno::Sequence< rendering::ARGBColor > aRes(nLen/4); + rendering::ARGBColor* pOut( aRes.getArray() ); + for( std::size_t i=0; i<nLen; i+=4 ) + { + *pOut++ = rendering::ARGBColor(pIn[3],pIn[0],pIn[1],pIn[2]); + pIn += 4; + } + return aRes; + } + virtual uno::Sequence< rendering::ARGBColor > 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<rendering::XColorSpace*>(this), 0); + + uno::Sequence< rendering::ARGBColor > aRes(nLen/4); + rendering::ARGBColor* pOut( aRes.getArray() ); + for( std::size_t i=0; i<nLen; i+=4 ) + { + *pOut++ = rendering::ARGBColor(pIn[3],pIn[3]*pIn[0],pIn[3]*pIn[1],pIn[3]*pIn[2]); + pIn += 4; + } + return aRes; + } + virtual uno::Sequence< double > 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; i<nLen; ++i ) + { + *pColors++ = pIn->Red; + *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; i<nLen; ++i ) + { + *pColors++ = pIn->Red; + *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; i<nLen; ++i ) + { + *pColors++ = pIn->Red/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<double> SAL_CALL convertFromIntegerColorSpace( const uno::Sequence< ::sal_Int8 >& deviceColor, + const uno::Reference< rendering::XColorSpace >& targetColorSpace ) override + { + if( dynamic_cast<StandardColorSpace*>(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<rendering::XColorSpace*>(this), 0); + + uno::Sequence<double> aRes(nLen); + double* pOut( aRes.getArray() ); + for( std::size_t i=0; i<nLen; i+=4 ) + { + *pOut++ = vcl::unotools::toDoubleColor(*pIn++); + *pOut++ = vcl::unotools::toDoubleColor(*pIn++); + *pOut++ = vcl::unotools::toDoubleColor(*pIn++); + *pOut++ = vcl::unotools::toDoubleColor(*pIn++); + } + return aRes; + } + else + { + // TODO(P3): if we know anything about target + // colorspace, this can be greatly sped up + uno::Sequence<rendering::ARGBColor> 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<StandardColorSpace*>(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<rendering::ARGBColor> 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<rendering::XColorSpace*>(this), 0); + + uno::Sequence< rendering::RGBColor > aRes(nLen/4); + rendering::RGBColor* pOut( aRes.getArray() ); + for( std::size_t i=0; i<nLen; i+=4 ) + { + *pOut++ = rendering::RGBColor( + vcl::unotools::toDoubleColor(pIn[0]), + vcl::unotools::toDoubleColor(pIn[1]), + vcl::unotools::toDoubleColor(pIn[2])); + pIn += 4; + } + return aRes; + } + + virtual uno::Sequence< rendering::ARGBColor > 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<rendering::XColorSpace*>(this), 0); + + uno::Sequence< rendering::ARGBColor > aRes(nLen/4); + rendering::ARGBColor* pOut( aRes.getArray() ); + for( std::size_t i=0; i<nLen; i+=4 ) + { + *pOut++ = rendering::ARGBColor( + vcl::unotools::toDoubleColor(pIn[3]), + vcl::unotools::toDoubleColor(pIn[0]), + vcl::unotools::toDoubleColor(pIn[1]), + vcl::unotools::toDoubleColor(pIn[2])); + pIn += 4; + } + return aRes; + } + + virtual uno::Sequence< rendering::ARGBColor > 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<rendering::XColorSpace*>(this), 0); + + uno::Sequence< rendering::ARGBColor > aRes(nLen/4); + rendering::ARGBColor* pOut( aRes.getArray() ); + for( std::size_t i=0; i<nLen; i+=4 ) + { + const sal_Int8 nAlpha( pIn[3] ); + *pOut++ = rendering::ARGBColor( + vcl::unotools::toDoubleColor(nAlpha), + vcl::unotools::toDoubleColor(nAlpha*pIn[0]), + vcl::unotools::toDoubleColor(nAlpha*pIn[1]), + vcl::unotools::toDoubleColor(nAlpha*pIn[2])); + pIn += 4; + } + return aRes; + } + + virtual uno::Sequence< ::sal_Int8 > 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; i<nLen; ++i ) + { + *pColors++ = vcl::unotools::toByteColor(pIn->Red); + *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; i<nLen; ++i ) + { + *pColors++ = vcl::unotools::toByteColor(pIn->Red); + *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; i<nLen; ++i ) + { + *pColors++ = vcl::unotools::toByteColor(pIn->Red/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<rendering::ARGBColor> 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<rendering::XColorSpace*>(this), 0); + + uno::Sequence< rendering::RGBColor > aRes(nLen/4); + rendering::RGBColor* pOut( aRes.getArray() ); + for( std::size_t i=0; i<nLen; i+=4 ) + { + *pOut++ = rendering::RGBColor(pIn[0],pIn[1],pIn[2]); + pIn += 4; + } + return aRes; + } + virtual uno::Sequence< rendering::ARGBColor > 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<rendering::XColorSpace*>(this), 0); + + uno::Sequence< rendering::ARGBColor > aRes(nLen/4); + rendering::ARGBColor* pOut( aRes.getArray() ); + for( std::size_t i=0; i<nLen; i+=4 ) + { + *pOut++ = rendering::ARGBColor(1.0,pIn[0],pIn[1],pIn[2]); + pIn += 4; + } + return aRes; + } + virtual uno::Sequence< rendering::ARGBColor > 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<rendering::XColorSpace*>(this), 0); + + uno::Sequence< rendering::ARGBColor > aRes(nLen/4); + rendering::ARGBColor* pOut( aRes.getArray() ); + for( std::size_t i=0; i<nLen; i+=4 ) + { + *pOut++ = rendering::ARGBColor(1.0,pIn[0],pIn[1],pIn[2]); + pIn += 4; + } + return aRes; + } + virtual uno::Sequence< double > 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; i<nLen; ++i ) + { + *pColors++ = pIn->Red; + *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; i<nLen; ++i ) + { + *pColors++ = pIn->Red; + *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; i<nLen; ++i ) + { + *pColors++ = pIn->Red/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<double> SAL_CALL convertFromIntegerColorSpace( const uno::Sequence< ::sal_Int8 >& deviceColor, + const uno::Reference< rendering::XColorSpace >& targetColorSpace ) override + { + if( dynamic_cast<StandardNoAlphaColorSpace*>(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<rendering::XColorSpace*>(this), 0); + + uno::Sequence<double> aRes(nLen); + double* pOut( aRes.getArray() ); + for( std::size_t i=0; i<nLen; i+=4 ) + { + *pOut++ = vcl::unotools::toDoubleColor(*pIn++); + *pOut++ = vcl::unotools::toDoubleColor(*pIn++); + *pOut++ = vcl::unotools::toDoubleColor(*pIn++); + *pOut++ = 1.0; pIn++; + } + return aRes; + } + else + { + // TODO(P3): if we know anything about target + // colorspace, this can be greatly sped up + uno::Sequence<rendering::ARGBColor> 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<StandardNoAlphaColorSpace*>(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<rendering::ARGBColor> 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<rendering::XColorSpace*>(this), 0); + + uno::Sequence< rendering::RGBColor > aRes(nLen/4); + rendering::RGBColor* pOut( aRes.getArray() ); + for( std::size_t i=0; i<nLen; i+=4 ) + { + *pOut++ = rendering::RGBColor( + vcl::unotools::toDoubleColor(pIn[0]), + vcl::unotools::toDoubleColor(pIn[1]), + vcl::unotools::toDoubleColor(pIn[2])); + pIn += 4; + } + return aRes; + } + + virtual uno::Sequence< rendering::ARGBColor > 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<rendering::XColorSpace*>(this), 0); + + uno::Sequence< rendering::ARGBColor > aRes(nLen/4); + rendering::ARGBColor* pOut( aRes.getArray() ); + for( std::size_t i=0; i<nLen; i+=4 ) + { + *pOut++ = rendering::ARGBColor( + 1.0, + vcl::unotools::toDoubleColor(pIn[0]), + vcl::unotools::toDoubleColor(pIn[1]), + vcl::unotools::toDoubleColor(pIn[2])); + pIn += 4; + } + return aRes; + } + + virtual uno::Sequence< rendering::ARGBColor > 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<rendering::XColorSpace*>(this), 0); + + uno::Sequence< rendering::ARGBColor > aRes(nLen/4); + rendering::ARGBColor* pOut( aRes.getArray() ); + for( std::size_t i=0; i<nLen; i+=4 ) + { + *pOut++ = rendering::ARGBColor( + 1.0, + vcl::unotools::toDoubleColor(pIn[0]), + vcl::unotools::toDoubleColor(pIn[1]), + vcl::unotools::toDoubleColor(pIn[2])); + pIn += 4; + } + return aRes; + } + + virtual uno::Sequence< ::sal_Int8 > 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; i<nLen; ++i ) + { + *pColors++ = vcl::unotools::toByteColor(pIn->Red); + *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; i<nLen; ++i ) + { + *pColors++ = vcl::unotools::toByteColor(pIn->Red); + *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; i<nLen; ++i ) + { + *pColors++ = vcl::unotools::toByteColor(pIn->Red/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; + } + }; + + } + + uno::Reference<rendering::XIntegerBitmapColorSpace> const & getStdColorSpace() + { + static uno::Reference<rendering::XIntegerBitmapColorSpace> SPACE = new StandardColorSpace(); + return SPACE; + } + + uno::Reference<rendering::XIntegerBitmapColorSpace> const & getStdColorSpaceWithoutAlpha() + { + static uno::Reference<rendering::XIntegerBitmapColorSpace> SPACE = new StandardNoAlphaColorSpace(); + return SPACE; + } + + 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<sal_Int8> colorToStdIntSequence( const ::Color& rColor ) + { + uno::Sequence<sal_Int8> aRet(4); + sal_Int8* pCols( aRet.getArray() ); +#ifdef OSL_BIGENDIAN + pCols[0] = rColor.GetRed(); + pCols[1] = rColor.GetGreen(); + pCols[2] = rColor.GetBlue(); + pCols[3] = rColor.GetAlpha(); +#else + *reinterpret_cast<sal_Int32*>(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<sal_Int32>(rRange.getX())), + (io_rDestPoint.getY() + + static_cast<sal_Int32>(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() ) + return o_rxParams; + + 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 = { uno::Any(xServiceInfo->getImplementationName()), + 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<vcl::Window> 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<int>( + 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<beans::PropertyValue>& 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 0000000000..46167dd9a8 --- /dev/null +++ b/canvas/source/tools/elapsedtime.cxx @@ -0,0 +1,130 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <canvas/elapsedtime.hxx> + +#include <tools/time.hxx> +#include <utility> + +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<ElapsedTime> pTimeBase ) + : m_pTimeBase(std::move( 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 0000000000..32eedb71b2 --- /dev/null +++ b/canvas/source/tools/page.cxx @@ -0,0 +1,133 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> +#include <basegfx/vector/b2ivector.hxx> +#include "page.hxx" + +namespace canvas +{ + Page::Page( const std::shared_ptr<IRenderModule> &rRenderModule ) : + mpRenderModule(rRenderModule), + mpSurface(rRenderModule->createSurface(::basegfx::B2IVector())) + { + } + + 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<PageFragment>(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. + std::erase(mpFragments, pFragment); + } + + 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.getWidth() + 1; + const sal_Int32 h = rect.maSize.getHeight() + 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. + basegfx::B2ISize aSize(mpRenderModule->getPageSize().getX(), mpRenderModule->getPageSize().getY()); + SurfaceRect aBoundary(aSize); + 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 0000000000..6af4bf0f6b --- /dev/null +++ b/canvas/source/tools/page.hxx @@ -0,0 +1,148 @@ +/* -*- 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 . + */ + +#pragma once + +#include <basegfx/vector/b2isize.hxx> +#include <basegfx/vector/b2ivector.hxx> +#include <basegfx/range/b2irectangle.hxx> +#include <rendering/icolorbuffer.hxx> +#include <rendering/irendermodule.hxx> +#include <rendering/isurface.hxx> + +#include <memory> +#include <vector> +#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<IRenderModule>& rRenderModule ); + + FragmentSharedPtr allocateSpace( const ::basegfx::B2ISize& rSize ); + bool nakedFragment( const FragmentSharedPtr& pFragment ); + void free( const FragmentSharedPtr& pFragment ); + const std::shared_ptr<ISurface>& getSurface() const { return mpSurface; } + bool isValid() const; + void validate(); + + private: + typedef std::vector<FragmentSharedPtr> FragmentContainer_t; + + std::shared_ptr<IRenderModule> mpRenderModule; + std::shared_ptr<ISurface> 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<IColorBuffer>& 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<ISurface> 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<ISurface> pSurface(mpPage->getSurface()); + + return pSurface->update( maRect.maPos, + ::basegfx::B2IRectangle( + maSourceOffset, + maSourceOffset + basegfx::B2IVector(maRect.maSize.getWidth(), maRect.maSize.getHeight())), + *mpBuffer ); + } + + private: + Page* mpPage; + SurfaceRect maRect; + std::shared_ptr<IColorBuffer> mpBuffer; + ::basegfx::B2IPoint maSourceOffset; + }; +} + +/* 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 0000000000..4ee7df76ed --- /dev/null +++ b/canvas/source/tools/pagemanager.cxx @@ -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 . + */ + +#include <sal/config.h> +#include <basegfx/vector/b2ivector.hxx> +#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<Page>(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<PageFragment>(rSize); + maFragments.push_back(pFragment); + return pFragment; + } + + void PageManager::free( const FragmentSharedPtr& pFragment ) + { + // erase the reference to the given fragment from our + // internal container. + std::erase(maFragments, pFragment); + + // 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.getWidth() * rSize.getHeight() ); + + 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().getX(), + mpRenderModule->getPageSize().getY() }; + } +} + +/* 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 0000000000..57de912a12 --- /dev/null +++ b/canvas/source/tools/pagemanager.hxx @@ -0,0 +1,76 @@ +/* -*- 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 . + */ + +#pragma once + +#include <basegfx/vector/b2isize.hxx> +#include <rendering/irendermodule.hxx> +#include <utility> + +#include "page.hxx" + +namespace canvas +{ + // PageManager + class PageManager + { + public: + explicit PageManager(std::shared_ptr<canvas::IRenderModule> xRenderModule) + : mpRenderModule(std::move(xRenderModule)) + { + } + + // returns the maximum size of a hardware + // accelerated page, e.g. OpenGL texture. + ::basegfx::B2ISize getPageSize() const; + + const std::shared_ptr<canvas::IRenderModule>& 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<canvas::IRenderModule> mpRenderModule; + + // here we collect all fragments that will be created + // since we need them for relocation purposes. + typedef std::vector<FragmentSharedPtr> FragmentContainer_t; + FragmentContainer_t maFragments; + + // this is the container holding all created pages, + // behind the scenes these are real hardware surfaces. + std::vector<PageSharedPtr> maPages; + + bool relocate( const FragmentSharedPtr& pFragment ); + }; + + + // PageManagerSharedPtr + + + typedef std::shared_ptr< PageManager > PageManagerSharedPtr; +} + +/* 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 0000000000..8c660a5e53 --- /dev/null +++ b/canvas/source/tools/parametricpolypolygon.cxx @@ -0,0 +1,236 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <basegfx/point/b2dpoint.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/range/b2drectangle.hxx> +#include <cppuhelper/supportsservice.hxx> + +#include <com/sun/star/rendering/XGraphicDevice.hpp> + +#include <parametricpolypolygon.hxx> +#include <utility> + +using namespace ::com::sun::star; + +namespace canvas +{ + uno::Sequence<OUString> ParametricPolyPolygon::getAvailableServiceNames() + { + return {"LinearGradient", + "EllipticalGradient", + "RectangularGradient"}; + } + + rtl::Reference<ParametricPolyPolygon> ParametricPolyPolygon::create( + const uno::Reference< rendering::XGraphicDevice >& rDevice, + std::u16string_view rServiceName, + const uno::Sequence< uno::Any >& rArgs ) + { + double fAspectRatio=1.0; + + // defaults + uno::Sequence< uno::Sequence< double > > colorSequence{ + rDevice->getDeviceColorSpace()->convertFromRGB({ rendering::RGBColor(0,0,0) }), + rDevice->getDeviceColorSpace()->convertFromRGB({ rendering::RGBColor(1,1,1) }) + }; + uno::Sequence< double > colorStops{ 0, 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 == u"LinearGradient" ) + { + return createLinearHorizontalGradient(rDevice, colorSequence, colorStops); + } + else if ( rServiceName == u"EllipticalGradient" ) + { + return createEllipticalGradient(rDevice, colorSequence, colorStops, fAspectRatio); + } + else if ( rServiceName == u"RectangularGradient" ) + { + return createRectangularGradient(rDevice, colorSequence, colorStops, fAspectRatio); + } + else if ( rServiceName == u"VerticalLineHatch" ) + { + // TODO: NYI + } + else if ( rServiceName == u"OrthogonalLinesHatch" ) + { + // TODO: NYI + } + else if ( rServiceName == u"ThreeCrossingLinesHatch" ) + { + // TODO: NYI + } + else if ( rServiceName == u"FourCrossingLinesHatch" ) + { + // TODO: NYI + } + + return nullptr; + } + + rtl::Reference<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 ); + } + + rtl::Reference<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 ); + } + + rtl::Reference<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 ParametricPolyPolygon::disposing(std::unique_lock<std::mutex>&) + { + 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() + { + std::unique_lock 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( uno::Reference< rendering::XGraphicDevice > xDevice, + const ::basegfx::B2DPolygon& rGradientPoly, + GradientType eType, + const uno::Sequence< uno::Sequence< double > >& rColors, + const uno::Sequence< double >& rStops, + double nAspectRatio ) : + mxDevice(std::move( xDevice )), + maValues( rGradientPoly, + rColors, + rStops, + nAspectRatio, + eType ) + { + } + + ParametricPolyPolygon::ParametricPolyPolygon( uno::Reference< rendering::XGraphicDevice > xDevice, + GradientType eType, + const uno::Sequence< uno::Sequence< double > >& rColors, + const uno::Sequence< double >& rStops ) : + mxDevice(std::move( xDevice )), + maValues( ::basegfx::B2DPolygon(), + rColors, + rStops, + 1.0, + eType ) + { + } + + ParametricPolyPolygon::Values ParametricPolyPolygon::getValues() const + { + 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 0000000000..24a07cb7dc --- /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 <sal/config.h> + +#include <string_view> + +#include <propertysethelper.hxx> +#include <com/sun/star/beans/PropertyVetoException.hpp> +#include <com/sun/star/beans/UnknownPropertyException.hpp> + +using namespace ::com::sun::star; + +namespace canvas +{ + namespace + { + void throwUnknown( std::u16string_view aPropertyName ) + { + throw beans::UnknownPropertyException( + OUString::Concat("PropertySetHelper: property ") + + aPropertyName + " not found." + ); + } + + void throwVeto( std::u16string_view aPropertyName ) + { + throw beans::PropertyVetoException( + OUString::Concat("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() + { + } + + void PropertySetHelper::initProperties( InputMap&& rMap ) + { + mpMap.reset(); + maMapEntries = std::move(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( std::move(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 0000000000..06eb6d1de5 --- /dev/null +++ b/canvas/source/tools/spriteredrawmanager.cxx @@ -0,0 +1,483 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <algorithm> + +#include <basegfx/range/b2drectangle.hxx> +#include <basegfx/utils/canvastools.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <sal/log.hxx> + +#include <spriteredrawmanager.hxx> +#include <boost/range/adaptor/reversed.hpp> +#include <utility> + +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( Sprite::Reference rAffectedSprite ) : + mpAffectedSprite(std::move(rAffectedSprite)), + 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( maSprites.begin(), maSprites.end() ); + 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() + { + } + + 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 ) + { + std::erase(maSprites, rSprite); + } + + 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 0000000000..88d20bbcfe --- /dev/null +++ b/canvas/source/tools/surface.cxx @@ -0,0 +1,437 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <basegfx/polygon/b2dpolygonclipper.hxx> +#include <comphelper/scopeguard.hxx> +#include <utility> + +#include "surface.hxx" + +namespace canvas +{ + Surface::Surface( PageManagerSharedPtr rPageManager, + std::shared_ptr<IColorBuffer> xColorBuffer, + const ::basegfx::B2IPoint& rPos, + const ::basegfx::B2ISize& rSize ) : + mpColorBuffer(std::move(xColorBuffer)), + mpPageManager(std::move(rPageManager)), + maSourceOffset(rPos), + maSize(rSize), + mbIsDirty(true) + { + } + + Surface::~Surface() + { + if(mpFragment) + mpPageManager->free(mpFragment); + } + + void Surface::setColorBufferDirty() + { + mbIsDirty=true; + } + + basegfx::B2DRectangle Surface::getUVCoords() const + { + ::basegfx::B2ISize aPageSize(mpPageManager->getPageSize()); + ::basegfx::B2IPoint aDestOffset; + if( mpFragment ) + aDestOffset = mpFragment->getPos(); + + const double pw( aPageSize.getWidth() ); + const double ph( aPageSize.getHeight() ); + const double ox( aDestOffset.getX() ); + const double oy( aDestOffset.getY() ); + const double sx( maSize.getWidth() ); + const double sy( maSize.getHeight() ); + + return ::basegfx::B2DRectangle( ox/pw, + oy/ph, + (ox+sx)/pw, + (oy+sy)/ph ); + } + + basegfx::B2DRectangle Surface::getUVCoords( const ::basegfx::B2IPoint& rPos, + const ::basegfx::B2ISize& rSize ) const + { + ::basegfx::B2ISize aPageSize(mpPageManager->getPageSize()); + + const double pw( aPageSize.getWidth() ); + const double ph( aPageSize.getHeight() ); + const double ox( rPos.getX() ); + const double oy( rPos.getY() ); + const double sx( rSize.getWidth() ); + const double sy( rSize.getHeight() ); + + return ::basegfx::B2DRectangle( ox/pw, + oy/ph, + (ox+sx)/pw, + (oy+sy)/ph ); + } + + bool Surface::draw( double fAlpha, + const ::basegfx::B2DPoint& rPos, + const ::basegfx::B2DHomMatrix& rTransform ) + { + std::shared_ptr<IRenderModule> pRenderModule(mpPageManager->getRenderModule()); + + RenderModuleGuard aGuard( pRenderModule ); + + prepareRendering(); + + // convert size to normalized device coordinates + const ::basegfx::B2DRectangle& rUV( getUVCoords() ); + + const double u1(rUV.getMinX()); + const double v1(rUV.getMinY()); + const double u2(rUV.getMaxX()); + const double v2(rUV.getMaxY()); + + // concat transforms + // 1) offset of surface subarea + // 2) surface transform + // 3) translation to output position [rPos] + // 4) scale to normalized device coordinates + // 5) flip y-axis + // 6) translate to account for viewport transform + basegfx::B2DHomMatrix aTransform(basegfx::utils::createTranslateB2DHomMatrix( + maSourceOffset.getX(), maSourceOffset.getY())); + aTransform = aTransform * rTransform; + aTransform.translate(::basegfx::fround(rPos.getX()), + ::basegfx::fround(rPos.getY())); + + /* + ###################################### + ###################################### + ###################################### + + Y + ^+1 + | + 2 | 3 + x------------x + | | | + | | | + ------|-----O------|------>X + -1 | | | +1 + | | | + x------------x + 1 | 0 + | + |-1 + + ###################################### + ###################################### + ###################################### + */ + + const ::basegfx::B2DPoint& p0(aTransform * ::basegfx::B2DPoint(maSize.getWidth(),maSize.getHeight())); + const ::basegfx::B2DPoint& p1(aTransform * ::basegfx::B2DPoint(0.0,maSize.getHeight())); + const ::basegfx::B2DPoint& p2(aTransform * ::basegfx::B2DPoint(0.0,0.0)); + const ::basegfx::B2DPoint& p3(aTransform * ::basegfx::B2DPoint(maSize.getWidth(),0.0)); + + canvas::Vertex vertex; + vertex.r = 1.0f; + vertex.g = 1.0f; + vertex.b = 1.0f; + vertex.a = static_cast<float>(fAlpha); + vertex.z = 0.0f; + + { + pRenderModule->beginPrimitive( canvas::IRenderModule::PrimitiveType::Quad ); + + // issue an endPrimitive() when leaving the scope + const ::comphelper::ScopeGuard aScopeGuard( + [&pRenderModule]() mutable { pRenderModule->endPrimitive(); } ); + + vertex.u=static_cast<float>(u2); vertex.v=static_cast<float>(v2); + vertex.x=static_cast<float>(p0.getX()); vertex.y=static_cast<float>(p0.getY()); + pRenderModule->pushVertex(vertex); + + vertex.u=static_cast<float>(u1); vertex.v=static_cast<float>(v2); + vertex.x=static_cast<float>(p1.getX()); vertex.y=static_cast<float>(p1.getY()); + pRenderModule->pushVertex(vertex); + + vertex.u=static_cast<float>(u1); vertex.v=static_cast<float>(v1); + vertex.x=static_cast<float>(p2.getX()); vertex.y=static_cast<float>(p2.getY()); + pRenderModule->pushVertex(vertex); + + vertex.u=static_cast<float>(u2); vertex.v=static_cast<float>(v1); + vertex.x=static_cast<float>(p3.getX()); vertex.y=static_cast<float>(p3.getY()); + pRenderModule->pushVertex(vertex); + } + + return !(pRenderModule->isError()); + } + + bool Surface::drawRectangularArea( + double fAlpha, + const ::basegfx::B2DPoint& rPos, + const ::basegfx::B2DRectangle& rArea, + const ::basegfx::B2DHomMatrix& rTransform ) + { + if( rArea.isEmpty() ) + return true; // immediate exit for empty area + + std::shared_ptr<IRenderModule> pRenderModule(mpPageManager->getRenderModule()); + + RenderModuleGuard aGuard( pRenderModule ); + + prepareRendering(); + + // these positions are relative to the texture + ::basegfx::B2IPoint aPos1( + ::basegfx::fround(rArea.getMinimum().getX()), + ::basegfx::fround(rArea.getMinimum().getY())); + ::basegfx::B2IPoint aPos2( + ::basegfx::fround(rArea.getMaximum().getX()), + ::basegfx::fround(rArea.getMaximum().getY()) ); + + // clip the positions to the area this surface covers + aPos1.setX(std::max(aPos1.getX(), maSourceOffset.getX())); + aPos1.setY(std::max(aPos1.getY(), maSourceOffset.getY())); + aPos2.setX(std::min(aPos2.getX(), maSourceOffset.getX() + maSize.getWidth())); + aPos2.setY(std::min(aPos2.getY(), maSourceOffset.getY() + maSize.getHeight())); + + // if the resulting area is empty, return immediately + ::basegfx::B2IVector aSize(aPos2 - aPos1); + if(aSize.getX() <= 0 || aSize.getY() <= 0) + return true; + + ::basegfx::B2IPoint aDestOffset; + if( mpFragment ) + aDestOffset = mpFragment->getPos(); + + // convert size to normalized device coordinates + const ::basegfx::B2DRectangle& rUV( + getUVCoords(aPos1 - maSourceOffset + aDestOffset, + basegfx::B2ISize(aSize.getX(), aSize.getY())) ); + const double u1(rUV.getMinX()); + const double v1(rUV.getMinY()); + const double u2(rUV.getMaxX()); + const double v2(rUV.getMaxY()); + + // concatenate transforms + // 1) offset of surface subarea + // 2) surface transform + // 3) translation to output position [rPos] + basegfx::B2DHomMatrix aTransform(basegfx::utils::createTranslateB2DHomMatrix(aPos1.getX(), aPos1.getY())); + aTransform = aTransform * rTransform; + aTransform.translate(::basegfx::fround(rPos.getX()), + ::basegfx::fround(rPos.getY())); + + + /* + ###################################### + ###################################### + ###################################### + + Y + ^+1 + | + 2 | 3 + x------------x + | | | + | | | + ------|-----O------|------>X + -1 | | | +1 + | | | + x------------x + 1 | 0 + | + |-1 + + ###################################### + ###################################### + ###################################### + */ + + const ::basegfx::B2DPoint& p0(aTransform * ::basegfx::B2DPoint(aSize.getX(),aSize.getY())); + const ::basegfx::B2DPoint& p1(aTransform * ::basegfx::B2DPoint(0.0, aSize.getY())); + const ::basegfx::B2DPoint& p2(aTransform * ::basegfx::B2DPoint(0.0, 0.0)); + const ::basegfx::B2DPoint& p3(aTransform * ::basegfx::B2DPoint(aSize.getX(),0.0)); + + canvas::Vertex vertex; + vertex.r = 1.0f; + vertex.g = 1.0f; + vertex.b = 1.0f; + vertex.a = static_cast<float>(fAlpha); + vertex.z = 0.0f; + + { + pRenderModule->beginPrimitive( canvas::IRenderModule::PrimitiveType::Quad ); + + // issue an endPrimitive() when leaving the scope + const ::comphelper::ScopeGuard aScopeGuard( + [&pRenderModule]() mutable { pRenderModule->endPrimitive(); } ); + + vertex.u=static_cast<float>(u2); vertex.v=static_cast<float>(v2); + vertex.x=static_cast<float>(p0.getX()); vertex.y=static_cast<float>(p0.getY()); + pRenderModule->pushVertex(vertex); + + vertex.u=static_cast<float>(u1); vertex.v=static_cast<float>(v2); + vertex.x=static_cast<float>(p1.getX()); vertex.y=static_cast<float>(p1.getY()); + pRenderModule->pushVertex(vertex); + + vertex.u=static_cast<float>(u1); vertex.v=static_cast<float>(v1); + vertex.x=static_cast<float>(p2.getX()); vertex.y=static_cast<float>(p2.getY()); + pRenderModule->pushVertex(vertex); + + vertex.u=static_cast<float>(u2); vertex.v=static_cast<float>(v1); + vertex.x=static_cast<float>(p3.getX()); vertex.y=static_cast<float>(p3.getY()); + pRenderModule->pushVertex(vertex); + } + + return !(pRenderModule->isError()); + } + + bool Surface::drawWithClip( double fAlpha, + const ::basegfx::B2DPoint& rPos, + const ::basegfx::B2DPolygon& rClipPoly, + const ::basegfx::B2DHomMatrix& rTransform ) + { + std::shared_ptr<IRenderModule> pRenderModule(mpPageManager->getRenderModule()); + + RenderModuleGuard aGuard( pRenderModule ); + + prepareRendering(); + + // untransformed surface rectangle, relative to the whole + // image (note: this surface might actually only be a tile of + // the whole image, with non-zero maSourceOffset) + const double x1(maSourceOffset.getX()); + const double y1(maSourceOffset.getY()); + const double w(maSize.getWidth()); + const double h(maSize.getHeight()); + const double x2(x1+w); + const double y2(y1+h); + const ::basegfx::B2DRectangle aSurfaceClipRect(x1,y1,x2,y2); + + // concatenate transforms + // we use 'fround' here to avoid rounding errors. the vertices will + // be transformed by the overall transform and uv coordinates will + // be calculated from the result, and this is why we need to use + // integer coordinates here... + basegfx::B2DHomMatrix aTransform = rTransform; + aTransform.translate(::basegfx::fround(rPos.getX()), + ::basegfx::fround(rPos.getY())); + + /* + ###################################### + ###################################### + ###################################### + + Y + ^+1 + | + 2 | 3 + x------------x + | | | + | | | + ------|-----O------|------>X + -1 | | | +1 + | | | + x------------x + 1 | 0 + | + |-1 + + ###################################### + ###################################### + ###################################### + */ + + // uv coordinates that map the surface rectangle + // to the destination rectangle. + const ::basegfx::B2DRectangle& rUV( getUVCoords() ); + + basegfx::B2DPolygon rTriangleList(basegfx::utils::clipTriangleListOnRange(rClipPoly, + aSurfaceClipRect)); + + // Push vertices to backend renderer + if(const sal_uInt32 nVertexCount = rTriangleList.count()) + { + canvas::Vertex vertex; + vertex.r = 1.0f; + vertex.g = 1.0f; + vertex.b = 1.0f; + vertex.a = static_cast<float>(fAlpha); + vertex.z = 0.0f; + + pRenderModule->beginPrimitive( canvas::IRenderModule::PrimitiveType::Triangle ); + + // issue an endPrimitive() when leaving the scope + const ::comphelper::ScopeGuard aScopeGuard( + [&pRenderModule]() mutable { pRenderModule->endPrimitive(); } ); + + for(sal_uInt32 nIndex=0; nIndex<nVertexCount; ++nIndex) + { + const basegfx::B2DPoint &aPoint = rTriangleList.getB2DPoint(nIndex); + basegfx::B2DPoint aTransformedPoint(aTransform * aPoint); + const double tu(((aPoint.getX()-aSurfaceClipRect.getMinX())*rUV.getWidth()/w)+rUV.getMinX()); + const double tv(((aPoint.getY()-aSurfaceClipRect.getMinY())*rUV.getHeight()/h)+rUV.getMinY()); + vertex.u=static_cast<float>(tu); + vertex.v=static_cast<float>(tv); + vertex.x=static_cast<float>(aTransformedPoint.getX()); + vertex.y=static_cast<float>(aTransformedPoint.getY()); + pRenderModule->pushVertex(vertex); + } + } + + return !(pRenderModule->isError()); + } + + void Surface::prepareRendering() + { + mpPageManager->validatePages(); + + // clients requested to draw from this surface, therefore one + // of the above implemented concrete rendering operations + // was triggered. we therefore need to ask the pagemanager + // to allocate some space for the fragment we're dedicated to. + if(!mpFragment) + { + mpFragment = mpPageManager->allocateSpace(maSize); + if( mpFragment ) + { + mpFragment->setColorBuffer(mpColorBuffer); + mpFragment->setSourceOffset(maSourceOffset); + } + } + + if( mpFragment ) + { + // now we need to 'select' the fragment, which will in turn + // pull information from the image on demand. + // in case this fragment is still not located on any of the + // available pages ['naked'], we force the page manager to + // do it now, no way to defer this any longer... + if(!(mpFragment->select(mbIsDirty))) + mpPageManager->nakedFragment(mpFragment); + + } + mbIsDirty=false; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/tools/surface.hxx b/canvas/source/tools/surface.hxx new file mode 100644 index 0000000000..e78925292f --- /dev/null +++ b/canvas/source/tools/surface.hxx @@ -0,0 +1,143 @@ +/* -*- 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 . + */ + +#pragma once + +#include <basegfx/point/b2ipoint.hxx> +#include <basegfx/point/b2dpoint.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/range/b2drectangle.hxx> +#include <basegfx/vector/b2isize.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <rendering/icolorbuffer.hxx> + +#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( PageManagerSharedPtr xPageManager, + std::shared_ptr<IColorBuffer> xColorBuffer, + 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<IColorBuffer> 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; +} + +/* 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 0000000000..af76b8b3c3 --- /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 <sal/config.h> +#include <sal/log.hxx> + +#include <basegfx/polygon/b2dpolypolygon.hxx> +#include <basegfx/polygon/b2dpolygontriangulator.hxx> +#include <basegfx/polygon/b2dpolypolygontools.hxx> +#include <utility> + +#include "surfaceproxy.hxx" + +namespace canvas +{ + SurfaceProxy::SurfaceProxy( std::shared_ptr<canvas::IColorBuffer> xBuffer, + PageManagerSharedPtr xPageManager ) : + mpPageManager(std::move( xPageManager )), + mpBuffer(std::move( xBuffer )) + { + const ::basegfx::B2ISize aImageSize(mpBuffer->getWidth(),mpBuffer->getHeight()); + const ::basegfx::B2ISize aPageSize(mpPageManager->getPageSize()); + const sal_Int32 aPageSizeX(aPageSize.getWidth()); + const sal_Int32 aPageSizeY(aPageSize.getHeight()); + const sal_Int32 aImageSizeX(aImageSize.getWidth()); + const sal_Int32 aImageSizeY(aImageSize.getHeight()); + + // 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<aImageSizeY; y+=aPageSizeY) + for(sal_Int32 x=0; x<aImageSizeX; x+=aPageSizeX) + ++dwNumSurfaces; + maSurfaceList.reserve(dwNumSurfaces); + + for(sal_Int32 y=0; y<aImageSizeY; y+=aPageSizeY) + { + for(sal_Int32 x=0; x<aImageSizeX; x+=aPageSizeX) + { + // the current surface is located at the position [x,y] + // and has the size [min(restx,pagesizex),min(resty,pagesizey) + ::basegfx::B2IPoint aOffset(x,y); + ::basegfx::B2ISize aSize( std::min( aImageSize.getWidth()-x, + aPageSize.getWidth() ), + std::min( aImageSize.getHeight()-y, + aPageSize.getHeight() ) ); + + maSurfaceList.push_back( + std::make_shared<Surface>( + 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 0000000000..72841d754e --- /dev/null +++ b/canvas/source/tools/surfaceproxy.hxx @@ -0,0 +1,118 @@ +/* -*- 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 . + */ + +#pragma once + +#include <rendering/isurfaceproxy.hxx> +#include <rendering/icolorbuffer.hxx> + +#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( std::shared_ptr<canvas::IColorBuffer> xBuffer, + PageManagerSharedPtr xPageManager ); + + // 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<SurfaceSharedPtr> 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<canvas::IColorBuffer> mpBuffer; + }; +} + +/* 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 0000000000..b60c5ecb48 --- /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 <sal/config.h> + +#include <rendering/isurfaceproxy.hxx> +#include <rendering/isurfaceproxymanager.hxx> + +#include "surfaceproxy.hxx" + +namespace canvas +{ + namespace { + + class SurfaceProxyManager : public ISurfaceProxyManager + { + public: + + explicit SurfaceProxyManager( const std::shared_ptr<IRenderModule>& rRenderModule ) : + mpPageManager( std::make_shared<PageManager>(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<ISurfaceProxy> createSurfaceProxy( const std::shared_ptr<IColorBuffer>& 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<SurfaceProxy>(pBuffer,mpPageManager); + } + + private: + PageManagerSharedPtr mpPageManager; + }; + + } + + std::shared_ptr<ISurfaceProxyManager> createSurfaceProxyManager( const std::shared_ptr<IRenderModule>& rRenderModule ) + { + return std::make_shared<SurfaceProxyManager>(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 0000000000..0925fa8e6f --- /dev/null +++ b/canvas/source/tools/surfacerect.hxx @@ -0,0 +1,90 @@ +/* -*- 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 . + */ + +#pragma once + +#include <basegfx/point/b2ipoint.hxx> +#include <basegfx/vector/b2isize.hxx> + +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.getWidth()); + const sal_Int32 y2(y1 + maSize.getHeight()); + 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.getWidth() - 1); + const sal_Int32 y1h(y1 + maSize.getHeight() - 1); + + const sal_Int32 x2(r.maPos.getX()); + const sal_Int32 y2(r.maPos.getY()); + const sal_Int32 x2w(x2 + r.maSize.getWidth() - 1); + const sal_Int32 y2h(y2 + r.maSize.getHeight() - 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.getWidth() - 1); + const sal_Int32 y2(y1 + maSize.getHeight() - 1); + if(!(r.pointInside(x1,y1))) return false; + if(!(r.pointInside(x2,y2))) return false; + return true; + } + }; +} + +/* 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 0000000000..ae0704d81d --- /dev/null +++ b/canvas/source/tools/verifyinput.cxx @@ -0,0 +1,709 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <basegfx/range/b2irange.hxx> +#include <basegfx/utils/canvastools.hxx> +#include <com/sun/star/geometry/AffineMatrix2D.hpp> +#include <com/sun/star/geometry/IntegerPoint2D.hpp> +#include <com/sun/star/geometry/IntegerSize2D.hpp> +#include <com/sun/star/geometry/Matrix2D.hpp> +#include <com/sun/star/geometry/RealBezierSegment2D.hpp> +#include <com/sun/star/geometry/RealPoint2D.hpp> +#include <com/sun/star/geometry/RealRectangle2D.hpp> +#include <com/sun/star/geometry/RealSize2D.hpp> +#include <com/sun/star/lang/IndexOutOfBoundsException.hpp> +#include <com/sun/star/rendering/CompositeOperation.hpp> +#include <com/sun/star/rendering/FontRequest.hpp> +#include <com/sun/star/rendering/IntegerBitmapLayout.hpp> +#include <com/sun/star/rendering/PathCapType.hpp> +#include <com/sun/star/rendering/PathJoinType.hpp> +#include <com/sun/star/rendering/RenderState.hpp> +#include <com/sun/star/rendering/Texture.hpp> +#include <com/sun/star/rendering/TexturingMode.hpp> +#include <com/sun/star/rendering/ViewState.hpp> +#include <com/sun/star/util/Endianness.hpp> + +#include <verifyinput.hxx> + + +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<sal_Int32>(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<sal_Int32>(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<sal_Int32>(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 ) + { + throw lang::IllegalArgumentException( + OUString::createFromAscii(mpStr) + + ": verifyInput(): one of stroke attributes' DashArray value out of range (is " + + OUString::number(rVal) + ")", + mrIf, mnArgPos ); + } + } + + 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<sal_Int32>(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<sal_Int32>(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<sal_Int32>(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<sal_Int32>(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: */ diff --git a/canvas/source/vcl/backbuffer.cxx b/canvas/source/vcl/backbuffer.cxx new file mode 100644 index 0000000000..aa45a56e7a --- /dev/null +++ b/canvas/source/vcl/backbuffer.cxx @@ -0,0 +1,59 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <vcl/svapp.hxx> + +#include "backbuffer.hxx" +#include "impltools.hxx" + + +namespace vclcanvas +{ + BackBuffer::BackBuffer( const OutputDevice& rRefDevice ) : + maVDev( VclPtr<VirtualDevice>::Create( rRefDevice, DeviceFormat::WITHOUT_ALPHA ) ) + { + tools::SetDefaultDeviceAntiAliasing( maVDev ); + } + + BackBuffer::~BackBuffer() + { + SolarMutexGuard aGuard; + maVDev.disposeAndClear(); + } + + OutputDevice& BackBuffer::getOutDev() + { + return *maVDev; + } + + const OutputDevice& BackBuffer::getOutDev() const + { + return *maVDev; + } + + void BackBuffer::setSize( const ::Size& rNewSize ) + { + maVDev->SetOutputSizePixel( rNewSize ); + } + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/vcl/backbuffer.hxx b/canvas/source/vcl/backbuffer.hxx new file mode 100644 index 0000000000..0e31111b6f --- /dev/null +++ b/canvas/source/vcl/backbuffer.hxx @@ -0,0 +1,50 @@ +/* -*- 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 . + */ + +#pragma once + +#include <vcl/virdev.hxx> + +#include "outdevprovider.hxx" + +#include <memory> + +namespace vclcanvas +{ + /// Background buffer abstraction + class BackBuffer : public OutDevProvider + { + public: + /** Create a backbuffer for given reference device */ + BackBuffer( const OutputDevice& rRefDevice ); + virtual ~BackBuffer() override; + + virtual OutputDevice& getOutDev() override; + virtual const OutputDevice& getOutDev() const override; + + void setSize( const ::Size& rNewSize ); + + private: + VclPtr< VirtualDevice > maVDev; + }; + + typedef std::shared_ptr< BackBuffer > BackBufferSharedPtr; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/vcl/bitmapbackbuffer.cxx b/canvas/source/vcl/bitmapbackbuffer.cxx new file mode 100644 index 0000000000..3766276f3b --- /dev/null +++ b/canvas/source/vcl/bitmapbackbuffer.cxx @@ -0,0 +1,147 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <osl/diagnose.h> +#include <vcl/bitmapex.hxx> +#include <vcl/svapp.hxx> + +#include "bitmapbackbuffer.hxx" +#include "impltools.hxx" + +namespace vclcanvas +{ + BitmapBackBuffer::BitmapBackBuffer( const BitmapEx& rBitmap, + const OutputDevice& rRefDevice ) : + maBitmap( rBitmap ), + mpVDev( nullptr ), + mrRefDevice( rRefDevice ), + mbBitmapContentIsCurrent( false ), + mbVDevContentIsCurrent( false ) + { + } + + BitmapBackBuffer::~BitmapBackBuffer() + { + // make sure solar mutex is held on deletion (other methods + // are supposed to be called with already locked solar mutex) + SolarMutexGuard aGuard; + + mpVDev.disposeAndClear(); + } + + OutputDevice& BitmapBackBuffer::getOutDev() + { + createVDev(); + updateVDev(); + return *mpVDev; + } + + const OutputDevice& BitmapBackBuffer::getOutDev() const + { + createVDev(); + updateVDev(); + return *mpVDev; + } + + void BitmapBackBuffer::clear() + { + // force current content to bitmap, make all transparent white + getBitmapReference().Erase(COL_TRANSPARENT); + } + + BitmapEx& BitmapBackBuffer::getBitmapReference() + { + OSL_ENSURE( !mbBitmapContentIsCurrent || !mbVDevContentIsCurrent, + "BitmapBackBuffer::getBitmapReference(): Both bitmap and VDev are valid?!" ); + + if( mbVDevContentIsCurrent && mpVDev ) + { + // VDev content is more current than bitmap - copy contents before! + mpVDev->EnableMapMode( false ); + mpVDev->SetAntialiasing( AntialiasingFlags::Enable ); + const Point aEmptyPoint; + *maBitmap = mpVDev->GetBitmapEx( aEmptyPoint, + mpVDev->GetOutputSizePixel() ); + } + + // client queries bitmap, and will possibly alter content - + // next time, VDev needs to be updated + mbBitmapContentIsCurrent = true; + mbVDevContentIsCurrent = false; + + return *maBitmap; + } + + Size BitmapBackBuffer::getBitmapSizePixel() const + { + Size aSize = maBitmap->GetSizePixel(); + + if( mbVDevContentIsCurrent && mpVDev ) + { + mpVDev->EnableMapMode( false ); + mpVDev->SetAntialiasing( AntialiasingFlags::Enable ); + aSize = mpVDev->GetOutputSizePixel(); + } + + return aSize; + } + + void BitmapBackBuffer::createVDev() const + { + if( mpVDev ) + return; + + // VDev not yet created, do it now. Create an alpha-VDev, + // if bitmap has transparency. + mpVDev = maBitmap->IsAlpha() ? + VclPtr<VirtualDevice>::Create( mrRefDevice, DeviceFormat::WITH_ALPHA ) : + VclPtr<VirtualDevice>::Create( mrRefDevice ); + + OSL_ENSURE( mpVDev, + "BitmapBackBuffer::createVDev(): Unable to create VirtualDevice" ); + + mpVDev->SetOutputSizePixel( maBitmap->GetSizePixel() ); + + tools::SetDefaultDeviceAntiAliasing( mpVDev ); + } + + void BitmapBackBuffer::updateVDev() const + { + OSL_ENSURE( !mbBitmapContentIsCurrent || !mbVDevContentIsCurrent, + "BitmapBackBuffer::updateVDev(): Both bitmap and VDev are valid?!" ); + + if( mpVDev && mbBitmapContentIsCurrent ) + { + // fill with bitmap content + mpVDev->EnableMapMode( false ); + mpVDev->SetAntialiasing( AntialiasingFlags::Enable ); + const Point aEmptyPoint; + mpVDev->DrawBitmapEx( aEmptyPoint, *maBitmap ); + } + + // canvas queried the VDev, and will possibly paint into + // it. Next time, bitmap must be updated + mbBitmapContentIsCurrent = false; + mbVDevContentIsCurrent = true; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/vcl/bitmapbackbuffer.hxx b/canvas/source/vcl/bitmapbackbuffer.hxx new file mode 100644 index 0000000000..9098e7e938 --- /dev/null +++ b/canvas/source/vcl/bitmapbackbuffer.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 . + */ + +#pragma once + +#include <vcl/virdev.hxx> +#include <vcl/bitmapex.hxx> + +#include <vclwrapper.hxx> +#include "outdevprovider.hxx" + +#include <memory> + +namespace vclcanvas +{ + /** Backbuffer implementation for canvas bitmap. + + This class abstracts away the renderable bitmap for the bitmap + canvas. The actual VirtualDevice is only created when + necessary, which makes read-only bitmaps a lot smaller. + */ + class BitmapBackBuffer : public OutDevProvider + { + public: + /** Create a backbuffer for given reference device + */ + BitmapBackBuffer( const BitmapEx& rBitmap, + const OutputDevice& rRefDevice ); + + virtual ~BitmapBackBuffer() override; + + virtual OutputDevice& getOutDev() override; + virtual const OutputDevice& getOutDev() const override; + + /// Clear the underlying bitmap to white, all transparent + void clear(); + + /** Exposing our internal bitmap. Only to be used from + CanvasBitmapHelper + + @internal + */ + BitmapEx& getBitmapReference(); + Size getBitmapSizePixel() const; + + private: + void createVDev() const; + void updateVDev() const; + + ::canvas::vcltools::VCLObject<BitmapEx> maBitmap; + mutable VclPtr<VirtualDevice> mpVDev; // created only on demand + + const OutputDevice& mrRefDevice; + + /** When true, the bitmap contains the last valid + content. When false, and mbVDevContentIsCurrent is true, + the VDev contains the last valid content (which must be + copied back to the bitmap, when getBitmapReference() is + called). When both are false, this object is just + initialized. + */ + mutable bool mbBitmapContentIsCurrent; + + /** When true, and mpVDev is non-NULL, the VDev contains the + last valid content. When false, and + mbBitmapContentIsCurrent is true, the bitmap contains the + last valid content. When both are false, this object is + just initialized. + */ + mutable bool mbVDevContentIsCurrent; + }; + + typedef std::shared_ptr< BitmapBackBuffer > BitmapBackBufferSharedPtr; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/vcl/cachedbitmap.cxx b/canvas/source/vcl/cachedbitmap.cxx new file mode 100644 index 0000000000..a27bf6166f --- /dev/null +++ b/canvas/source/vcl/cachedbitmap.cxx @@ -0,0 +1,91 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <com/sun/star/rendering/XCanvas.hpp> +#include <com/sun/star/rendering/RepaintResult.hpp> +#include <utility> +#include <comphelper/diagnose_ex.hxx> + +#include "cachedbitmap.hxx" +#include "repainttarget.hxx" + + +using namespace ::com::sun::star; + +namespace vclcanvas +{ + CachedBitmap::CachedBitmap( GraphicObjectSharedPtr xGraphicObject, + const ::Point& rPoint, + const ::Size& rSize, + const GraphicAttr& rAttr, + const rendering::ViewState& rUsedViewState, + rendering::RenderState aUsedRenderState, + const uno::Reference< rendering::XCanvas >& rTarget ) : + CachedPrimitiveBase( rUsedViewState, rTarget ), + mpGraphicObject(std::move( xGraphicObject )), + maRenderState(std::move(aUsedRenderState)), + maPoint( rPoint ), + maSize( rSize ), + maAttributes( rAttr ) + { + } + + void CachedBitmap::disposing(std::unique_lock<std::mutex>& rGuard) + { + mpGraphicObject.reset(); + + CachedPrimitiveBase::disposing(rGuard); + } + + ::sal_Int8 CachedBitmap::doRedraw( const rendering::ViewState& rNewState, + const rendering::ViewState& rOldState, + const uno::Reference< rendering::XCanvas >& rTargetCanvas, + bool bSameViewTransform ) + { + ENSURE_OR_THROW( bSameViewTransform, + "CachedBitmap::doRedraw(): base called with changed view transform " + "(told otherwise during construction)" ); + + // TODO(P1): Could adapt to modified clips as well + if( rNewState.Clip != rOldState.Clip ) + return rendering::RepaintResult::FAILED; + + RepaintTarget* pTarget = dynamic_cast< RepaintTarget* >(rTargetCanvas.get()); + + ENSURE_OR_THROW( pTarget, + "CachedBitmap::redraw(): cannot cast target to RepaintTarget" ); + + if( !pTarget->repaint( mpGraphicObject, + rNewState, + maRenderState, + maPoint, + maSize, + maAttributes ) ) + { + // target failed to repaint + return rendering::RepaintResult::FAILED; + } + + return rendering::RepaintResult::REDRAWN; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/vcl/cachedbitmap.hxx b/canvas/source/vcl/cachedbitmap.hxx new file mode 100644 index 0000000000..140b9c968f --- /dev/null +++ b/canvas/source/vcl/cachedbitmap.hxx @@ -0,0 +1,66 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/rendering/RenderState.hpp> +#include <base/cachedprimitivebase.hxx> +#include <vcl/GraphicObject.hxx> +#include <memory> + + +/* Definition of CachedBitmap class */ + +namespace vclcanvas +{ + typedef std::shared_ptr< GraphicObject > GraphicObjectSharedPtr; + + class CachedBitmap : public ::canvas::CachedPrimitiveBase + { + public: + + /** Create an XCachedPrimitive for given GraphicObject + */ + CachedBitmap( GraphicObjectSharedPtr xGraphicObject, + const ::Point& rPoint, + const ::Size& rSize, + const GraphicAttr& rAttr, + const css::rendering::ViewState& rUsedViewState, + css::rendering::RenderState aUsedRenderState, + const css::uno::Reference< css::rendering::XCanvas >& rTarget ); + + /// Dispose all internal references + virtual void disposing(std::unique_lock<std::mutex>& rGuard) override; + + private: + virtual ::sal_Int8 doRedraw( const css::rendering::ViewState& rNewState, + const css::rendering::ViewState& rOldState, + const css::uno::Reference< css::rendering::XCanvas >& rTargetCanvas, + bool bSameViewTransform ) override; + + + GraphicObjectSharedPtr mpGraphicObject; + const css::rendering::RenderState maRenderState; + const ::Point maPoint; + const ::Size maSize; + const GraphicAttr maAttributes; + }; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/vcl/canvas.cxx b/canvas/source/vcl/canvas.cxx new file mode 100644 index 0000000000..9408a4dbb6 --- /dev/null +++ b/canvas/source/vcl/canvas.cxx @@ -0,0 +1,134 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include "canvas.hxx" + +#include <com/sun/star/lang/NoSupportException.hpp> +#include <sal/log.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <vcl/outdev.hxx> + +#include "outdevholder.hxx" + +using namespace ::com::sun::star; + +namespace vclcanvas +{ + Canvas::Canvas( const uno::Sequence< uno::Any >& aArguments, + const uno::Reference< uno::XComponentContext >& /*rxContext*/ ) : + maArguments(aArguments) + { + } + + void Canvas::initialize() + { + // #i64742# Only perform initialization when not in probe mode + if( !maArguments.hasElements() ) + return; + + /* maArguments: + 0: ptr to creating instance (Window or VirtualDevice) + 1: current bounds of creating instance + 2: bool, denoting always on top state for Window (always false for VirtualDevice) + 3: XWindow for creating Window (or empty for VirtualDevice) + 4: SystemGraphicsData as a streamed Any + */ + SolarMutexGuard aGuard; + + SAL_INFO("canvas.vcl", "VCLCanvas::initialize called" ); + + ENSURE_ARG_OR_THROW( maArguments.getLength() >= 5 && + maArguments[0].getValueTypeClass() == uno::TypeClass_HYPER, + "Canvas::initialize: wrong number of arguments, or wrong types" ); + + sal_Int64 nPtr = 0; + maArguments[0] >>= nPtr; + + OutputDevice* pOutDev = reinterpret_cast<OutputDevice*>(nPtr); + if( !pOutDev ) + throw lang::NoSupportException("Passed OutDev invalid!", nullptr); + + OutDevProviderSharedPtr pOutdevProvider = std::make_shared<OutDevHolder>(*pOutDev); + + // setup helper + maDeviceHelper.init( pOutdevProvider ); + maCanvasHelper.init( *this, + pOutdevProvider, + true, // OutDev state preservation + false ); // no alpha on surface + + maArguments.realloc(0); + } + + Canvas::~Canvas() + { + SAL_INFO("canvas.vcl", "VCLCanvas destroyed" ); + } + + void Canvas::disposeThis() + { + SolarMutexGuard aGuard; + + // forward to parent + CanvasBaseT::disposeThis(); + } + + OUString SAL_CALL Canvas::getServiceName( ) + { + return "com.sun.star.rendering.Canvas.VCL"; + } + + OUString Canvas::getImplementationName() { + return "com.sun.star.comp.rendering.Canvas.VCL"; + } + + sal_Bool Canvas::supportsService(OUString const & ServiceName) { + return cppu::supportsService(this, ServiceName); + } + + css::uno::Sequence<OUString> Canvas::getSupportedServiceNames() { + return {getServiceName()}; + } + + bool Canvas::repaint( const GraphicObjectSharedPtr& rGrf, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState, + const ::Point& rPt, + const ::Size& rSz, + const GraphicAttr& rAttr ) const + { + SolarMutexGuard aGuard; + + return maCanvasHelper.repaint( rGrf, viewState, renderState, rPt, rSz, rAttr ); + } +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +com_sun_star_comp_rendering_Canvas_VCL_get_implementation( + css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const& args) +{ + rtl::Reference<vclcanvas::Canvas> p = new vclcanvas::Canvas(args, context); + p->initialize(); + return cppu::acquire(p.get()); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/vcl/canvas.hxx b/canvas/source/vcl/canvas.hxx new file mode 100644 index 0000000000..8bcbc1f498 --- /dev/null +++ b/canvas/source/vcl/canvas.hxx @@ -0,0 +1,118 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/lang/XServiceName.hpp> +#include <com/sun/star/util/XUpdatable.hpp> +#include <com/sun/star/rendering/XBitmapCanvas.hpp> +#include <com/sun/star/rendering/XIntegerBitmap.hpp> +#include <com/sun/star/rendering/XGraphicDevice.hpp> + +#include <cppuhelper/compbase.hxx> +#include <comphelper/uno3.hxx> + +#include <base/basemutexhelper.hxx> +#include <base/bitmapcanvasbase.hxx> +#include <base/integerbitmapbase.hxx> +#include <base/graphicdevicebase.hxx> + +#include "canvashelper.hxx" +#include "impltools.hxx" +#include "devicehelper.hxx" +#include "repainttarget.hxx" + +namespace vclcanvas +{ + typedef ::cppu::WeakComponentImplHelper< css::rendering::XBitmapCanvas, + css::rendering::XIntegerBitmap, + css::rendering::XGraphicDevice, + css::lang::XMultiServiceFactory, + css::util::XUpdatable, + css::beans::XPropertySet, + css::lang::XServiceName, + css::lang::XServiceInfo > GraphicDeviceBase_Base; + typedef ::canvas::GraphicDeviceBase< ::canvas::BaseMutexHelper< GraphicDeviceBase_Base >, + DeviceHelper, + tools::LocalGuard, + ::cppu::OWeakObject > CanvasBase_Base; + typedef ::canvas::IntegerBitmapBase< + canvas::BitmapCanvasBase2< + CanvasBase_Base, + CanvasHelper, + tools::LocalGuard, + ::cppu::OWeakObject> > CanvasBaseT; + + /** Product of this component's factory. + + The Canvas object combines the actual Window canvas with + the XGraphicDevice interface. This is because there's a + one-to-one relation between them, anyway, since each window + can have exactly one canvas and one associated + XGraphicDevice. And to avoid messing around with circular + references, this is implemented as one single object. + */ + class Canvas : public CanvasBaseT, + public RepaintTarget + { + public: + Canvas( const css::uno::Sequence< + css::uno::Any >& aArguments, + const css::uno::Reference< + css::uno::XComponentContext >& rxContext ); + + void initialize(); + + /// For resource tracking + virtual ~Canvas() override; + + /// Dispose all internal references + virtual void disposeThis() override; + + // Forwarding the XComponent implementation to the + // cppu::ImplHelper templated base + // Classname Base doing refcounting Base implementing the XComponent interface + // | | | + // V V V + DECLARE_UNO3_XCOMPONENT_AGG_DEFAULTS( Canvas, GraphicDeviceBase_Base, ::cppu::WeakComponentImplHelperBase ) + + // XServiceName + virtual OUString SAL_CALL getServiceName( ) override; + + OUString SAL_CALL getImplementationName() override; + sal_Bool SAL_CALL supportsService(OUString const & ServiceName) override; + css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override; + + // RepaintTarget + virtual bool repaint( const GraphicObjectSharedPtr& rGrf, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState, + const ::Point& rPt, + const ::Size& rSz, + const GraphicAttr& rAttr ) const override; + + private: + css::uno::Sequence< css::uno::Any > maArguments; + }; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/vcl/canvasbitmap.cxx b/canvas/source/vcl/canvasbitmap.cxx new file mode 100644 index 0000000000..9ae37c2c49 --- /dev/null +++ b/canvas/source/vcl/canvasbitmap.cxx @@ -0,0 +1,122 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <cppuhelper/supportsservice.hxx> + +#include "canvasbitmap.hxx" + + +using namespace ::com::sun::star; + + +namespace vclcanvas +{ + // Currently, the only way to generate an XBitmap is from + // XGraphicDevice.getCompatibleBitmap(). Therefore, we don't even + // take a bitmap here, but a VDev directly. + CanvasBitmap::CanvasBitmap( const ::Size& rSize, + bool bAlphaBitmap, + rendering::XGraphicDevice& rDevice, + const OutDevProviderSharedPtr& rOutDevProvider ) + { + // create bitmap for given reference device + // ======================================== + Bitmap aBitmap(rSize, vcl::PixelFormat::N24_BPP); + + // only create alpha channel bitmap, if factory requested + // that. Providing alpha-channeled bitmaps by default has, + // especially under VCL, a huge performance penalty (have to + // use alpha VDev, then). + if( bAlphaBitmap ) + { + AlphaMask aAlpha ( rSize ); + + maCanvasHelper.init( BitmapEx( aBitmap, aAlpha ), + rDevice, + rOutDevProvider ); + } + else + { + maCanvasHelper.init( BitmapEx( aBitmap ), + rDevice, + rOutDevProvider ); + } + } + + CanvasBitmap::CanvasBitmap( const BitmapEx& rBitmap, + rendering::XGraphicDevice& rDevice, + const OutDevProviderSharedPtr& rOutDevProvider ) + { + maCanvasHelper.init( rBitmap, rDevice, rOutDevProvider ); + } + + + OUString SAL_CALL CanvasBitmap::getImplementationName( ) + { + return "VCLCanvas.CanvasBitmap"; + } + + sal_Bool SAL_CALL CanvasBitmap::supportsService( const OUString& ServiceName ) + { + return cppu::supportsService( this, ServiceName ); + } + + uno::Sequence< OUString > SAL_CALL CanvasBitmap::getSupportedServiceNames( ) + { + return { "com.sun.star.rendering.CanvasBitmap" }; + } + + BitmapEx CanvasBitmap::getBitmap() const + { + SolarMutexGuard aGuard; + + // TODO(T3): Rework to use shared_ptr all over the place for + // BmpEx. This is highly un-threadsafe + return maCanvasHelper.getBitmap(); + } + + bool CanvasBitmap::repaint( const GraphicObjectSharedPtr& rGrf, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState, + const ::Point& rPt, + const ::Size& rSz, + const GraphicAttr& rAttr ) const + { + SolarMutexGuard aGuard; + + mbSurfaceDirty = true; + + return maCanvasHelper.repaint( rGrf, viewState, renderState, rPt, rSz, rAttr ); + } + + uno::Any SAL_CALL CanvasBitmap::getFastPropertyValue( sal_Int32 nHandle ) + { + if( nHandle == 0 ) { + BitmapEx* pBitmapEx = new BitmapEx( getBitmap() ); + + return uno::Any( reinterpret_cast<sal_Int64>( pBitmapEx ) ); + } + + return uno::Any( sal_Int64(0) ); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/vcl/canvasbitmap.hxx b/canvas/source/vcl/canvasbitmap.hxx new file mode 100644 index 0000000000..1a95ce8c63 --- /dev/null +++ b/canvas/source/vcl/canvasbitmap.hxx @@ -0,0 +1,117 @@ +/* -*- 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 . + */ + +#pragma once + +#include <cppuhelper/compbase.hxx> + +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/rendering/XBitmapCanvas.hpp> +#include <com/sun/star/rendering/XIntegerBitmap.hpp> +#include <com/sun/star/beans/XFastPropertySet.hpp> + +#include <vcl/bitmapex.hxx> + +#include <base/bitmapcanvasbase.hxx> +#include <base/basemutexhelper.hxx> +#include <base/integerbitmapbase.hxx> +#include "canvasbitmaphelper.hxx" + +#include "impltools.hxx" +#include "repainttarget.hxx" + + +/* Definition of CanvasBitmap class */ + +namespace vclcanvas +{ + typedef ::cppu::WeakComponentImplHelper< css::rendering::XBitmapCanvas, + css::rendering::XIntegerBitmap, + css::lang::XServiceInfo, + css::beans::XFastPropertySet > CanvasBitmapBase_Base; + typedef ::canvas::IntegerBitmapBase< + canvas::BitmapCanvasBase2< + ::canvas::BaseMutexHelper< CanvasBitmapBase_Base >, + CanvasBitmapHelper, + tools::LocalGuard, + ::cppu::OWeakObject> > CanvasBitmap_Base; + + class CanvasBitmap : public CanvasBitmap_Base, + public RepaintTarget + { + public: + /** Must be called with locked Solar mutex + + @param rSize + Size in pixel of the bitmap to generate + + @param bAlphaBitmap + When true, bitmap will have an alpha channel + + @param rDevice + Reference device, with which bitmap should be compatible + */ + CanvasBitmap( const ::Size& rSize, + bool bAlphaBitmap, + css::rendering::XGraphicDevice& rDevice, + const OutDevProviderSharedPtr& rOutDevProvider ); + + /// Must be called with locked Solar mutex + CanvasBitmap( const BitmapEx& rBitmap, + css::rendering::XGraphicDevice& rDevice, + const OutDevProviderSharedPtr& rOutDevProvider ); + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName( ) override; + virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames( ) override; + + // RepaintTarget interface + virtual bool repaint( const GraphicObjectSharedPtr& rGrf, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState, + const ::Point& rPt, + const ::Size& rSz, + const GraphicAttr& rAttr ) const override; + + /// Not threadsafe! Returned object is shared! + BitmapEx getBitmap() const; + + // XFastPropertySet + // used to retrieve BitmapEx pointer or X Pixmap handles for this bitmap + // handle values have these meanings: + // 0 ... get pointer to BitmapEx + // 1 ... get X pixmap handle to rgb content + // 2 ... get X pixmap handle to alpha mask + // returned any contains either BitmapEx pointer or array of three Any value + // 1st a bool value: true - free the pixmap after used by XFreePixmap, false do nothing, the pixmap is used internally in the canvas + // 2nd the pixmap handle (sal_Int64) + // 3rd the pixmap depth + virtual css::uno::Any SAL_CALL getFastPropertyValue(sal_Int32 nHandle) override; + virtual void SAL_CALL setFastPropertyValue(sal_Int32, const css::uno::Any&) override {} + + private: + /** MUST hold here, too, since CanvasHelper only contains a + raw pointer (without refcounting) + */ + css::uno::Reference<css::rendering::XGraphicDevice> mxDevice; + }; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/vcl/canvasbitmaphelper.cxx b/canvas/source/vcl/canvasbitmaphelper.cxx new file mode 100644 index 0000000000..99b9831cab --- /dev/null +++ b/canvas/source/vcl/canvasbitmaphelper.cxx @@ -0,0 +1,176 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> +#include <sal/log.hxx> + +#include <canvas/canvastools.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <vcl/bitmapex.hxx> +#include <vcl/BitmapTools.hxx> +#include <vcl/canvastools.hxx> + +#include "canvasbitmap.hxx" +#include "canvasbitmaphelper.hxx" + + +using namespace ::com::sun::star; + +namespace vclcanvas +{ + CanvasBitmapHelper::CanvasBitmapHelper() + { + } + + void CanvasBitmapHelper::init( const BitmapEx& rBitmap, + rendering::XGraphicDevice& rDevice, + const OutDevProviderSharedPtr& rOutDevReference ) + { + mpOutDevReference = rOutDevReference; + mpBackBuffer = std::make_shared<BitmapBackBuffer>( rBitmap, rOutDevReference->getOutDev() ); + + // forward new settings to base class (ref device, output + // surface, no protection (own backbuffer), alpha depends on + // whether BmpEx is transparent or not) + CanvasHelper::init( rDevice, + mpBackBuffer, + false, + rBitmap.IsAlpha() ); + } + + void CanvasBitmapHelper::disposing() + { + mpBackBuffer.reset(); + mpOutDevReference.reset(); + + // forward to base class + CanvasHelper::disposing(); + } + + geometry::IntegerSize2D CanvasBitmapHelper::getSize() const + { + if( !mpBackBuffer ) + return geometry::IntegerSize2D(); + + return vcl::unotools::integerSize2DFromSize( mpBackBuffer->getBitmapSizePixel() ); + } + + void CanvasBitmapHelper::clear() + { + // are we disposed? + if( mpBackBuffer ) + mpBackBuffer->clear(); // alpha vdev needs special treatment + } + + uno::Reference< rendering::XBitmap > CanvasBitmapHelper::getScaledBitmap( const geometry::RealSize2D& newSize, + bool beFast ) + { + ENSURE_OR_THROW( mpDevice, + "disposed CanvasHelper" ); + + SAL_INFO( "canvas.vcl", "::vclcanvas::CanvasBitmapHelper::getScaledBitmap()" ); + + if( !mpBackBuffer || mpDevice ) + return uno::Reference< rendering::XBitmap >(); // we're disposed + + BitmapEx aRes( mpBackBuffer->getBitmapReference() ); + + aRes.Scale( vcl::unotools::sizeFromRealSize2D(newSize), + beFast ? BmpScaleFlag::Default : BmpScaleFlag::BestQuality ); + + return uno::Reference< rendering::XBitmap >( + new CanvasBitmap( aRes, *mpDevice, mpOutDevReference ) ); + } + + uno::Sequence< sal_Int8 > CanvasBitmapHelper::getData( rendering::IntegerBitmapLayout& rLayout, + const geometry::IntegerRectangle2D& rect ) + { + SAL_INFO( "canvas.vcl", "::vclcanvas::CanvasBitmapHelper::getData()" ); + + if( !mpBackBuffer ) + return uno::Sequence< sal_Int8 >(); // we're disposed + + rLayout = getMemoryLayout(); + + // TODO(F1): Support more formats. + const Size aBmpSize( mpBackBuffer->getBitmapReference().GetSizePixel() ); + + rLayout.ScanLines = aBmpSize.Height(); + rLayout.ScanLineBytes = aBmpSize.Width()*4; + rLayout.ScanLineStride = rLayout.ScanLineBytes; + + uno::Sequence< sal_Int8 > aRes = vcl::bitmap::CanvasExtractBitmapData(mpBackBuffer->getBitmapReference(), rect); + return aRes; + } + + uno::Sequence< sal_Int8 > CanvasBitmapHelper::getPixel( rendering::IntegerBitmapLayout& rLayout, + const geometry::IntegerPoint2D& pos ) + { + SAL_INFO( "canvas.vcl", "::vclcanvas::CanvasBitmapHelper::getPixel()" ); + + if( !mpBackBuffer ) + return uno::Sequence< sal_Int8 >(); // we're disposed + + rLayout = getMemoryLayout(); + rLayout.ScanLines = 1; + rLayout.ScanLineBytes = 4; + rLayout.ScanLineStride = rLayout.ScanLineBytes; + + const Size aBmpSize( mpBackBuffer->getBitmapReference().GetSizePixel() ); + + ENSURE_ARG_OR_THROW( pos.X >= 0 && pos.X < aBmpSize.Width(), + "X coordinate out of bounds" ); + ENSURE_ARG_OR_THROW( pos.Y >= 0 && pos.Y < aBmpSize.Height(), + "Y coordinate out of bounds" ); + + ::Color aColor = mpBackBuffer->getBitmapReference().GetPixelColor(pos.X, pos.Y); + + uno::Sequence< sal_Int8 > aRes( 4 ); + sal_Int8* pRes = aRes.getArray(); + pRes[ 0 ] = aColor.GetRed(); + pRes[ 1 ] = aColor.GetGreen(); + pRes[ 2 ] = aColor.GetBlue(); + pRes[ 3 ] = aColor.GetAlpha(); + + return aRes; + } + + rendering::IntegerBitmapLayout CanvasBitmapHelper::getMemoryLayout() const + { + if( !mpOutDevProvider ) + return rendering::IntegerBitmapLayout(); // we're disposed + + rendering::IntegerBitmapLayout aBitmapLayout( ::canvas::tools::getStdMemoryLayout(getSize()) ); + if ( !hasAlpha() ) + aBitmapLayout.ColorSpace = canvas::tools::getStdColorSpaceWithoutAlpha(); + + return aBitmapLayout; + } + + BitmapEx CanvasBitmapHelper::getBitmap() const + { + if( !mpBackBuffer ) + return BitmapEx(); // we're disposed + else + return mpBackBuffer->getBitmapReference(); + } + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/vcl/canvasbitmaphelper.hxx b/canvas/source/vcl/canvasbitmaphelper.hxx new file mode 100644 index 0000000000..7e75f92e82 --- /dev/null +++ b/canvas/source/vcl/canvasbitmaphelper.hxx @@ -0,0 +1,106 @@ +/* -*- 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 . + */ + +#pragma once + +#include "canvashelper.hxx" + +#include <vcl/bitmapex.hxx> + +#include "bitmapbackbuffer.hxx" + + +namespace vclcanvas +{ + /** Helper class for basic canvasbitmap functionality. Extends + CanvasHelper with some CanvasBitmap specialities, such as alpha + support. + + Note that a plain CanvasHelper, although it does support the + XBitmap interface, has no provision for alpha channel on VCL + (at least no efficient one. If the alpha VDev one day gets + part of SAL, we might change that). + */ + class CanvasBitmapHelper : public CanvasHelper + { + public: + CanvasBitmapHelper(); + + /** Set a new bitmap on this helper. + + This method late-initializes the bitmap canvas helper, + providing it with the necessary device and output + objects. The internally stored bitmap representation is + updated from the given bitmap, including any size + changes. Note that the CanvasHelper does <em>not</em> take + ownership of the SpriteCanvas object, nor does it perform + any reference counting. Thus, to prevent reference counted + objects from deletion, the user of this class is + responsible for holding ref-counted references to those + objects! + + @param rBitmap + Content of this bitmap is used as our new content (our + internal size is adapted to the size of the bitmap given) + + @param rDevice + Reference device for this canvas bitmap + + @param rOutDevProvider + Reference output device. Used to create matching bitmap. + */ + void init( const BitmapEx& rBitmap, + css::rendering::XGraphicDevice& rDevice, + const OutDevProviderSharedPtr& rOutDevProvider ); + + + // Overridden CanvasHelper functionality + // ===================================== + + void disposing(); + + void clear(); + + css::geometry::IntegerSize2D getSize() const; + + css::uno::Reference< css::rendering::XBitmap > + getScaledBitmap( const css::geometry::RealSize2D& newSize, + bool beFast ); + + css::uno::Sequence< sal_Int8 > + getData( css::rendering::IntegerBitmapLayout& bitmapLayout, + const css::geometry::IntegerRectangle2D& rect ); + + css::uno::Sequence< sal_Int8 > + getPixel( css::rendering::IntegerBitmapLayout& bitmapLayout, + const css::geometry::IntegerPoint2D& pos ); + + css::rendering::IntegerBitmapLayout getMemoryLayout() const; + + /// @internal + BitmapEx getBitmap() const; + + private: + + BitmapBackBufferSharedPtr mpBackBuffer; + OutDevProviderSharedPtr mpOutDevReference; + }; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/vcl/canvascustomsprite.cxx b/canvas/source/vcl/canvascustomsprite.cxx new file mode 100644 index 0000000000..02e9b47fb2 --- /dev/null +++ b/canvas/source/vcl/canvascustomsprite.cxx @@ -0,0 +1,158 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#include <sal/config.h> + +#include <basegfx/point/b2dpoint.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <vcl/outdev.hxx> + +#include "canvascustomsprite.hxx" + +using namespace ::com::sun::star; + + +namespace vclcanvas +{ + + CanvasCustomSprite::CanvasCustomSprite( const geometry::RealSize2D& rSpriteSize, + rendering::XGraphicDevice& rDevice, + const ::canvas::SpriteSurface::Reference& rOwningSpriteCanvas, + const OutDevProviderSharedPtr& rOutDevProvider, + bool bShowSpriteBounds ) + { + ENSURE_OR_THROW( rOwningSpriteCanvas && + rOutDevProvider, + "CanvasCustomSprite::CanvasCustomSprite(): Invalid sprite canvas" ); + + // setup back buffer + + + const ::Size aSize( + static_cast<sal_Int32>( std::max( 1.0, + ceil( rSpriteSize.Width ))), // round up to nearest int, + // enforce sprite to have at + // least (1,1) pixel size + static_cast<sal_Int32>( std::max( 1.0, + ceil( rSpriteSize.Height ))) ); + + // create content backbuffer in screen depth + BackBufferSharedPtr pBackBuffer = std::make_shared<BackBuffer>( rOutDevProvider->getOutDev() ); + pBackBuffer->setSize( aSize ); + + // create mask backbuffer + BackBufferSharedPtr pBackBufferMask = std::make_shared<BackBuffer>( rOutDevProvider->getOutDev() ); + pBackBufferMask->setSize( aSize ); + + // TODO(F1): Implement alpha vdev (could prolly enable + // antialiasing again, then) + + // disable font antialiasing (causes ugly shadows otherwise) + pBackBuffer->getOutDev().SetAntialiasing( AntialiasingFlags::DisableText ); + pBackBufferMask->getOutDev().SetAntialiasing( AntialiasingFlags::DisableText ); + + // set mask vdev drawmode, such that everything is painted + // black. That leaves us with a binary image, white for + // background, black for painted content + pBackBufferMask->getOutDev().SetDrawMode( DrawModeFlags::BlackLine | DrawModeFlags::BlackFill | DrawModeFlags::BlackText | + DrawModeFlags::BlackGradient | DrawModeFlags::BlackBitmap ); + + + // setup canvas helper + + + // always render into back buffer, don't preserve state (it's + // our private VDev, after all), have notion of alpha + maCanvasHelper.init( rDevice, + pBackBuffer, + false, + true ); + maCanvasHelper.setBackgroundOutDev( pBackBufferMask ); + + + // setup sprite helper + + + maSpriteHelper.init( rSpriteSize, + rOwningSpriteCanvas, + pBackBuffer, + pBackBufferMask, + bShowSpriteBounds ); + + // clear sprite to 100% transparent + maCanvasHelper.clear(); + } + + OUString SAL_CALL CanvasCustomSprite::getImplementationName() + { + return "VCLCanvas.CanvasCustomSprite"; + } + + sal_Bool SAL_CALL CanvasCustomSprite::supportsService( const OUString& ServiceName ) + { + return cppu::supportsService( this, ServiceName ); + } + + uno::Sequence< OUString > SAL_CALL CanvasCustomSprite::getSupportedServiceNames() + { + return { "com.sun.star.rendering.CanvasCustomSprite" }; + } + + // Sprite + void CanvasCustomSprite::redraw( OutputDevice& rOutDev, + bool bBufferedUpdate ) const + { + SolarMutexGuard aGuard; + + redraw( rOutDev, maSpriteHelper.getPosPixel(), bBufferedUpdate ); + } + + void CanvasCustomSprite::redraw( OutputDevice& rOutDev, + const ::basegfx::B2DPoint& rOrigOutputPos, + bool bBufferedUpdate ) const + { + SolarMutexGuard aGuard; + + maSpriteHelper.redraw( rOutDev, + rOrigOutputPos, + mbSurfaceDirty, + bBufferedUpdate ); + + mbSurfaceDirty = false; + } + + bool CanvasCustomSprite::repaint( const GraphicObjectSharedPtr& rGrf, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState, + const ::Point& rPt, + const ::Size& rSz, + const GraphicAttr& rAttr ) const + { + SolarMutexGuard aGuard; + + mbSurfaceDirty = true; + + return maCanvasHelper.repaint( rGrf, viewState, renderState, rPt, rSz, rAttr ); + } + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/vcl/canvascustomsprite.hxx b/canvas/source/vcl/canvascustomsprite.hxx new file mode 100644 index 0000000000..1a7d3ccfb6 --- /dev/null +++ b/canvas/source/vcl/canvascustomsprite.hxx @@ -0,0 +1,116 @@ +/* -*- 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 . + */ + +#pragma once + +#include <cppuhelper/compbase.hxx> +#include <comphelper/uno3.hxx> + +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/rendering/XBitmapCanvas.hpp> +#include <com/sun/star/rendering/XCustomSprite.hpp> +#include <com/sun/star/rendering/XIntegerBitmap.hpp> + +#include <base/basemutexhelper.hxx> +#include <base/spritesurface.hxx> +#include <base/canvascustomspritebase.hxx> + +#include "sprite.hxx" +#include "canvashelper.hxx" +#include "spritehelper.hxx" +#include "impltools.hxx" +#include "repainttarget.hxx" + + +namespace vclcanvas +{ + typedef ::cppu::WeakComponentImplHelper< css::rendering::XCustomSprite, + css::rendering::XBitmapCanvas, + css::rendering::XIntegerBitmap, + css::lang::XServiceInfo > CanvasCustomSpriteBase_Base; + /** Mixin Sprite + + Have to mixin the Sprite interface before deriving from + ::canvas::CanvasCustomSpriteBase, as this template should + already implement some of those interface methods. + + The reason why this appears kinda convoluted is the fact that + we cannot specify non-IDL types as WeakComponentImplHelper + template args, and furthermore, don't want to derive + ::canvas::CanvasCustomSpriteBase directly from + ::canvas::Sprite (because derivees of + ::canvas::CanvasCustomSpriteBase have to explicitly forward + the XInterface methods (e.g. via DECLARE_UNO3_AGG_DEFAULTS) + anyway). Basically, ::canvas::CanvasCustomSpriteBase should + remain a base class that provides implementation, not to + enforce any specific interface on its derivees. + */ + class CanvasCustomSpriteSpriteBase_Base : public ::canvas::BaseMutexHelper< CanvasCustomSpriteBase_Base >, + public Sprite + { + }; + + typedef ::canvas::CanvasCustomSpriteBase< CanvasCustomSpriteSpriteBase_Base, + SpriteHelper, + CanvasHelper, + tools::LocalGuard, + ::cppu::OWeakObject > CanvasCustomSpriteBaseT; + + /* Definition of CanvasCustomSprite class */ + + class CanvasCustomSprite : public CanvasCustomSpriteBaseT, + public RepaintTarget + { + public: + CanvasCustomSprite( const css::geometry::RealSize2D& rSpriteSize, + css::rendering::XGraphicDevice& rDevice, + const ::canvas::SpriteSurface::Reference& rOwningSpriteCanvas, + const OutDevProviderSharedPtr& rOutDevProvider, + bool bShowSpriteBounds ); + + // Forwarding the XComponent implementation to the + // cppu::ImplHelper templated base + // Classname Base doing refcount Base implementing the XComponent interface + // | | | + // V V V + DECLARE_UNO3_XCOMPONENT_AGG_DEFAULTS( CanvasCustomSprite, CanvasCustomSpriteBase_Base, ::cppu::WeakComponentImplHelperBase ) + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override; + + // Sprite + virtual void redraw( OutputDevice& rOutDev, + bool bBufferedUpdate ) const override; + virtual void redraw( OutputDevice& rOutDev, + const ::basegfx::B2DPoint& rPos, + bool bBufferedUpdate ) const override; + + // RepaintTarget + virtual bool repaint( const GraphicObjectSharedPtr& rGrf, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState, + const ::Point& rPt, + const ::Size& rSz, + const GraphicAttr& rAttr ) const override; + }; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/vcl/canvasfont.cxx b/canvas/source/vcl/canvasfont.cxx new file mode 100644 index 0000000000..336f70d632 --- /dev/null +++ b/canvas/source/vcl/canvasfont.cxx @@ -0,0 +1,161 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <basegfx/numeric/ftools.hxx> +#include <canvas/canvastools.hxx> +#include <com/sun/star/rendering/PanoseProportion.hpp> +#include <cppuhelper/supportsservice.hxx> +#include <i18nlangtag/languagetag.hxx> +#include <rtl/math.hxx> +#include <vcl/metric.hxx> +#include <vcl/virdev.hxx> + +#include "canvasfont.hxx" +#include "textlayout.hxx" + +using namespace ::com::sun::star; + + +namespace vclcanvas +{ + CanvasFont::CanvasFont( const rendering::FontRequest& rFontRequest, + const uno::Sequence< beans::PropertyValue >& rExtraFontProperties, + const geometry::Matrix2D& rFontMatrix, + rendering::XGraphicDevice& rDevice, + const OutDevProviderSharedPtr& rOutDevProvider ) : + CanvasFont_Base( m_aMutex ), + maFont( vcl::Font( rFontRequest.FontDescription.FamilyName, + rFontRequest.FontDescription.StyleName, + Size( 0, ::basegfx::fround(rFontRequest.CellSize) ) ) ), + maFontRequest( rFontRequest ), + mpRefDevice( &rDevice ), + mpOutDevProvider( rOutDevProvider ), + maFontMatrix( rFontMatrix ) + { + maFont->SetAlignment( ALIGN_BASELINE ); + maFont->SetCharSet( (rFontRequest.FontDescription.IsSymbolFont==css::util::TriState_YES) ? RTL_TEXTENCODING_SYMBOL : RTL_TEXTENCODING_UNICODE ); + maFont->SetVertical( rFontRequest.FontDescription.IsVertical==css::util::TriState_YES ); + + // TODO(F2): improve panose->vclenum conversion + maFont->SetWeight( static_cast<FontWeight>(rFontRequest.FontDescription.FontDescription.Weight) ); + maFont->SetItalic( (rFontRequest.FontDescription.FontDescription.Letterform<=8) ? ITALIC_NONE : ITALIC_NORMAL ); + maFont->SetPitch( + rFontRequest.FontDescription.FontDescription.Proportion == rendering::PanoseProportion::MONO_SPACED + ? PITCH_FIXED : PITCH_VARIABLE); + + maFont->SetLanguage( LanguageTag::convertToLanguageType( rFontRequest.Locale, false)); + + // adjust to stretched/shrunk font + tools::setupFontWidth(rFontMatrix, maFont.get(), rOutDevProvider->getOutDev()); + + sal_uInt32 nEmphasisMark = 0; + + ::canvas::tools::extractExtraFontProperties(rExtraFontProperties, nEmphasisMark); + + if (nEmphasisMark) + maFont->SetEmphasisMark(FontEmphasisMark(nEmphasisMark)); + } + + void SAL_CALL CanvasFont::disposing() + { + SolarMutexGuard aGuard; + + mpOutDevProvider.reset(); + mpRefDevice.clear(); + } + + uno::Reference< rendering::XTextLayout > SAL_CALL CanvasFont::createTextLayout( const rendering::StringContext& aText, sal_Int8 nDirection, sal_Int64 ) + { + SolarMutexGuard aGuard; + + if( !mpRefDevice.is() ) + return uno::Reference< rendering::XTextLayout >(); // we're disposed + + return new TextLayout( aText, + nDirection, + Reference( this ), + mpRefDevice, + mpOutDevProvider); + } + + rendering::FontRequest SAL_CALL CanvasFont::getFontRequest( ) + { + return maFontRequest; + } + + rendering::FontMetrics SAL_CALL CanvasFont::getFontMetrics( ) + { + SolarMutexGuard aGuard; + + OutputDevice& rOutDev = mpOutDevProvider->getOutDev(); + ScopedVclPtrInstance< VirtualDevice > pVDev( rOutDev ); + pVDev->SetFont(getVCLFont()); + const ::FontMetric& aMetric( pVDev->GetFontMetric() ); + + return rendering::FontMetrics( + aMetric.GetAscent(), + aMetric.GetDescent(), + aMetric.GetInternalLeading(), + aMetric.GetExternalLeading(), + 0, + aMetric.GetDescent() / 2.0, + aMetric.GetAscent() / 2.0); + } + + uno::Sequence< double > SAL_CALL CanvasFont::getAvailableSizes( ) + { + // TODO(F1) + return uno::Sequence< double >(); + } + + uno::Sequence< beans::PropertyValue > SAL_CALL CanvasFont::getExtraFontProperties( ) + { + // TODO(F1) + return uno::Sequence< beans::PropertyValue >(); + } + + OUString SAL_CALL CanvasFont::getImplementationName() + { + return "VCLCanvas::CanvasFont"; + } + + sal_Bool SAL_CALL CanvasFont::supportsService( const OUString& ServiceName ) + { + return cppu::supportsService( this, ServiceName ); + } + + uno::Sequence< OUString > SAL_CALL CanvasFont::getSupportedServiceNames() + { + return { "com.sun.star.rendering.CanvasFont" }; + } + + vcl::Font const & CanvasFont::getVCLFont() const + { + return *maFont; + } + + const css::geometry::Matrix2D& CanvasFont::getFontMatrix() const + { + return maFontMatrix; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/vcl/canvasfont.hxx b/canvas/source/vcl/canvasfont.hxx new file mode 100644 index 0000000000..834df756e0 --- /dev/null +++ b/canvas/source/vcl/canvasfont.hxx @@ -0,0 +1,90 @@ +/* -*- 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 . + */ + +#pragma once + +#include <cppuhelper/compbase.hxx> +#include <cppuhelper/basemutex.hxx> + +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/geometry/Matrix2D.hpp> +#include <com/sun/star/rendering/FontRequest.hpp> +#include <com/sun/star/rendering/XCanvasFont.hpp> +#include <com/sun/star/rendering/XGraphicDevice.hpp> + +#include <vcl/font.hxx> + +#include <vclwrapper.hxx> + +#include "outdevprovider.hxx" + + +/* Definition of CanvasFont class */ + +namespace vclcanvas +{ + typedef ::cppu::WeakComponentImplHelper< css::rendering::XCanvasFont, + css::lang::XServiceInfo > CanvasFont_Base; + + class CanvasFont : public ::cppu::BaseMutex, + public CanvasFont_Base + { + public: + typedef rtl::Reference<CanvasFont> Reference; + + /// make noncopyable + CanvasFont(const CanvasFont&) = delete; + const CanvasFont& operator=(const CanvasFont&) = delete; + + CanvasFont( const css::rendering::FontRequest& fontRequest, + const css::uno::Sequence< css::beans::PropertyValue >& extraFontProperties, + const css::geometry::Matrix2D& rFontMatrix, + css::rendering::XGraphicDevice& rDevice, + const OutDevProviderSharedPtr& rOutDevProvider ); + + /// Dispose all internal references + virtual void SAL_CALL disposing() override; + + // XCanvasFont + virtual css::uno::Reference< css::rendering::XTextLayout > SAL_CALL createTextLayout( const css::rendering::StringContext& aText, sal_Int8 nDirection, sal_Int64 nRandomSeed ) override; + virtual css::rendering::FontRequest SAL_CALL getFontRequest( ) override; + virtual css::rendering::FontMetrics SAL_CALL getFontMetrics( ) override; + virtual css::uno::Sequence< double > SAL_CALL getAvailableSizes( ) override; + virtual css::uno::Sequence< css::beans::PropertyValue > SAL_CALL getExtraFontProperties( ) override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override; + + vcl::Font const & getVCLFont() const; + + const css::geometry::Matrix2D& getFontMatrix() const; + + private: + ::canvas::vcltools::VCLObject<vcl::Font> maFont; + css::rendering::FontRequest maFontRequest; + css::uno::Reference< css::rendering::XGraphicDevice> mpRefDevice; + OutDevProviderSharedPtr mpOutDevProvider; + css::geometry::Matrix2D maFontMatrix; + }; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/vcl/canvashelper.cxx b/canvas/source/vcl/canvashelper.cxx new file mode 100644 index 0000000000..1e47b02c84 --- /dev/null +++ b/canvas/source/vcl/canvashelper.cxx @@ -0,0 +1,1212 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/numeric/ftools.hxx> +#include <basegfx/point/b2dpoint.hxx> +#include <basegfx/polygon/b2dlinegeometry.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/range/b2drectangle.hxx> +#include <basegfx/utils/canvastools.hxx> +#include <basegfx/vector/b2dsize.hxx> +#include <com/sun/star/drawing/LineCap.hpp> +#include <com/sun/star/rendering/CompositeOperation.hpp> +#include <com/sun/star/rendering/PathCapType.hpp> +#include <com/sun/star/rendering/PathJoinType.hpp> +#include <com/sun/star/rendering/TextDirection.hpp> +#include <comphelper/sequence.hxx> +#include <rtl/math.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <tools/poly.hxx> +#include <vcl/bitmapex.hxx> +#include <vcl/BitmapReadAccess.hxx> +#include <vcl/canvastools.hxx> +#include <vcl/BitmapAlphaClampFilter.hxx> +#include <vcl/skia/SkiaHelper.hxx> + +#include <canvas/canvastools.hxx> + +#include "canvasbitmap.hxx" +#include "canvasfont.hxx" +#include "canvashelper.hxx" +#include "impltools.hxx" +#include "textlayout.hxx" + + +using namespace ::com::sun::star; + +namespace vclcanvas +{ + namespace + { + basegfx::B2DLineJoin b2DJoineFromJoin( sal_Int8 nJoinType ) + { + switch( nJoinType ) + { + case rendering::PathJoinType::NONE: + return basegfx::B2DLineJoin::NONE; + + case rendering::PathJoinType::MITER: + return basegfx::B2DLineJoin::Miter; + + case rendering::PathJoinType::ROUND: + return basegfx::B2DLineJoin::Round; + + case rendering::PathJoinType::BEVEL: + return basegfx::B2DLineJoin::Bevel; + + default: + ENSURE_OR_THROW( false, + "b2DJoineFromJoin(): Unexpected join type" ); + } + + return basegfx::B2DLineJoin::NONE; + } + + drawing::LineCap unoCapeFromCap( sal_Int8 nCapType) + { + switch ( nCapType) + { + case rendering::PathCapType::BUTT: + return drawing::LineCap_BUTT; + + case rendering::PathCapType::ROUND: + return drawing::LineCap_ROUND; + + case rendering::PathCapType::SQUARE: + return drawing::LineCap_SQUARE; + + default: + ENSURE_OR_THROW( false, + "unoCapeFromCap(): Unexpected cap type" ); + } + return drawing::LineCap_BUTT; + } + } + + CanvasHelper::CanvasHelper() : + mpDevice(), + mbHaveAlpha( false ) + { + } + + void CanvasHelper::disposing() + { + mpDevice = nullptr; + mpProtectedOutDevProvider.reset(); + mpOutDevProvider.reset(); + mp2ndOutDevProvider.reset(); + } + + void CanvasHelper::init( rendering::XGraphicDevice& rDevice, + const OutDevProviderSharedPtr& rOutDev, + bool bProtect, + bool bHaveAlpha ) + { + // cast away const, need to change refcount (as this is + // ~invisible to client code, still logically const) + mpDevice = &rDevice; + mbHaveAlpha = bHaveAlpha; + + setOutDev( rOutDev, bProtect ); + } + + void CanvasHelper::setOutDev( const OutDevProviderSharedPtr& rOutDev, + bool bProtect ) + { + if( bProtect ) + mpProtectedOutDevProvider = rOutDev; + else + mpProtectedOutDevProvider.reset(); + + mpOutDevProvider = rOutDev; + } + + void CanvasHelper::setBackgroundOutDev( const OutDevProviderSharedPtr& rOutDev ) + { + mp2ndOutDevProvider = rOutDev; + mp2ndOutDevProvider->getOutDev().EnableMapMode( false ); + mp2ndOutDevProvider->getOutDev().SetAntialiasing( AntialiasingFlags::Enable ); + } + + void CanvasHelper::clear() + { + // are we disposed? + if( !mpOutDevProvider ) + return; + + OutputDevice& rOutDev( mpOutDevProvider->getOutDev() ); + tools::OutDevStateKeeper aStateKeeper( mpProtectedOutDevProvider ); + + rOutDev.EnableMapMode( false ); + rOutDev.SetAntialiasing( AntialiasingFlags::Enable ); + rOutDev.SetLineColor( COL_WHITE ); + rOutDev.SetFillColor( COL_WHITE ); + rOutDev.SetClipRegion(); + rOutDev.DrawRect( ::tools::Rectangle( Point(), + rOutDev.GetOutputSizePixel()) ); + + if( !mp2ndOutDevProvider ) + return; + + OutputDevice& rOutDev2( mp2ndOutDevProvider->getOutDev() ); + + rOutDev2.SetDrawMode( DrawModeFlags::Default ); + rOutDev2.EnableMapMode( false ); + rOutDev2.SetAntialiasing( AntialiasingFlags::Enable ); + rOutDev2.SetLineColor( COL_WHITE ); + rOutDev2.SetFillColor( COL_WHITE ); + rOutDev2.SetClipRegion(); + rOutDev2.DrawRect( ::tools::Rectangle( Point(), + rOutDev2.GetOutputSizePixel()) ); + rOutDev2.SetDrawMode( DrawModeFlags::BlackLine | DrawModeFlags::BlackFill | DrawModeFlags::BlackText | + DrawModeFlags::BlackGradient | DrawModeFlags::BlackBitmap ); + } + + void CanvasHelper::drawLine( const rendering::XCanvas* , + const geometry::RealPoint2D& aStartRealPoint2D, + const geometry::RealPoint2D& aEndRealPoint2D, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState ) + { + // are we disposed? + if( !mpOutDevProvider ) + return; + + // nope, render + tools::OutDevStateKeeper aStateKeeper( mpProtectedOutDevProvider ); + setupOutDevState( viewState, renderState, LINE_COLOR ); + + const Point aStartPoint( tools::mapRealPoint2D( aStartRealPoint2D, + viewState, renderState ) ); + const Point aEndPoint( tools::mapRealPoint2D( aEndRealPoint2D, + viewState, renderState ) ); + // TODO(F2): alpha + mpOutDevProvider->getOutDev().DrawLine( aStartPoint, aEndPoint ); + + if( mp2ndOutDevProvider ) + mp2ndOutDevProvider->getOutDev().DrawLine( aStartPoint, aEndPoint ); + } + + void CanvasHelper::drawBezier( const rendering::XCanvas* , + const geometry::RealBezierSegment2D& aBezierSegment, + const geometry::RealPoint2D& _aEndPoint, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState ) + { + if( !mpOutDevProvider ) + return; + + tools::OutDevStateKeeper aStateKeeper( mpProtectedOutDevProvider ); + setupOutDevState( viewState, renderState, LINE_COLOR ); + + const Point& rStartPoint( tools::mapRealPoint2D( geometry::RealPoint2D(aBezierSegment.Px, + aBezierSegment.Py), + viewState, renderState ) ); + const Point& rCtrlPoint1( tools::mapRealPoint2D( geometry::RealPoint2D(aBezierSegment.C1x, + aBezierSegment.C1y), + viewState, renderState ) ); + const Point& rCtrlPoint2( tools::mapRealPoint2D( geometry::RealPoint2D(aBezierSegment.C2x, + aBezierSegment.C2y), + viewState, renderState ) ); + const Point& rEndPoint( tools::mapRealPoint2D( _aEndPoint, + viewState, renderState ) ); + + ::tools::Polygon aPoly(4); + aPoly.SetPoint( rStartPoint, 0 ); + aPoly.SetFlags( 0, PolyFlags::Normal ); + aPoly.SetPoint( rCtrlPoint1, 1 ); + aPoly.SetFlags( 1, PolyFlags::Control ); + aPoly.SetPoint( rCtrlPoint2, 2 ); + aPoly.SetFlags( 2, PolyFlags::Control ); + aPoly.SetPoint( rEndPoint, 3 ); + aPoly.SetFlags( 3, PolyFlags::Normal ); + + // TODO(F2): alpha + mpOutDevProvider->getOutDev().DrawPolygon( aPoly ); + if( mp2ndOutDevProvider ) + mp2ndOutDevProvider->getOutDev().DrawPolygon( aPoly ); + } + + uno::Reference< rendering::XCachedPrimitive > CanvasHelper::drawPolyPolygon( const rendering::XCanvas* , + const uno::Reference< rendering::XPolyPolygon2D >& xPolyPolygon, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState ) + { + ENSURE_ARG_OR_THROW( xPolyPolygon.is(), + "polygon is NULL"); + + if( mpOutDevProvider ) + { + tools::OutDevStateKeeper aStateKeeper( mpProtectedOutDevProvider ); + setupOutDevState( viewState, renderState, LINE_COLOR ); + + const ::basegfx::B2DPolyPolygon& rPolyPoly( + ::basegfx::unotools::b2DPolyPolygonFromXPolyPolygon2D(xPolyPolygon) ); + const ::tools::PolyPolygon aPolyPoly( tools::mapPolyPolygon( rPolyPoly, viewState, renderState ) ); + + if( rPolyPoly.isClosed() ) + { + mpOutDevProvider->getOutDev().DrawPolyPolygon( aPolyPoly ); + + if( mp2ndOutDevProvider ) + mp2ndOutDevProvider->getOutDev().DrawPolyPolygon( aPolyPoly ); + } + else + { + // mixed open/closed state. Cannot render open polygon + // via DrawPolyPolygon(), since that implicitly + // closed every polygon. OTOH, no need to distinguish + // further and render closed polygons via + // DrawPolygon(), and open ones via DrawPolyLine(): + // closed polygons will simply already contain the + // closing segment. + sal_uInt16 nSize( aPolyPoly.Count() ); + + for( sal_uInt16 i=0; i<nSize; ++i ) + { + mpOutDevProvider->getOutDev().DrawPolyLine( aPolyPoly[i] ); + + if( mp2ndOutDevProvider ) + mp2ndOutDevProvider->getOutDev().DrawPolyLine( aPolyPoly[i] ); + } + } + } + + // TODO(P1): Provide caching here. + return uno::Reference< rendering::XCachedPrimitive >(nullptr); + } + + uno::Reference< rendering::XCachedPrimitive > CanvasHelper::strokePolyPolygon( const rendering::XCanvas* , + const uno::Reference< rendering::XPolyPolygon2D >& xPolyPolygon, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState, + const rendering::StrokeAttributes& strokeAttributes ) + { + ENSURE_ARG_OR_THROW( xPolyPolygon.is(), + "polygon is NULL"); + + if( mpOutDevProvider ) + { + tools::OutDevStateKeeper aStateKeeper( mpProtectedOutDevProvider ); + + ::basegfx::B2DHomMatrix aMatrix; + ::canvas::tools::mergeViewAndRenderTransform(aMatrix, viewState, renderState); + + ::basegfx::B2DPolyPolygon aPolyPoly( + ::basegfx::unotools::b2DPolyPolygonFromXPolyPolygon2D(xPolyPolygon) ); + + std::vector<double> aDashArray; + if( strokeAttributes.DashArray.hasElements() ) + aDashArray = ::comphelper::sequenceToContainer< std::vector<double> >(strokeAttributes.DashArray); + + // First try to draw directly using VCL. + bool directFailed = false; + setupOutDevState( viewState, renderState, LINE_COLOR ); + for( sal_uInt32 i=0; i<aPolyPoly.count(); ++i ) + { + if( !mpOutDevProvider->getOutDev().DrawPolyLineDirect( aMatrix, aPolyPoly.getB2DPolygon(i), + strokeAttributes.StrokeWidth, 0, !aDashArray.empty() ? &aDashArray : nullptr, + b2DJoineFromJoin(strokeAttributes.JoinType), unoCapeFromCap(strokeAttributes.StartCapType))) + { + directFailed = true; + break; + } + } + if(!directFailed) + return uno::Reference< rendering::XCachedPrimitive >(nullptr); + + // Do it all manually. + + // apply dashing, if any + if( strokeAttributes.DashArray.hasElements() ) + { + ::basegfx::B2DPolyPolygon aDashedPolyPoly; + + for( sal_uInt32 i=0; i<aPolyPoly.count(); ++i ) + { + // AW: new interface; You may also get gaps in the same run now + basegfx::utils::applyLineDashing(aPolyPoly.getB2DPolygon(i), aDashArray, &aDashedPolyPoly); + //aDashedPolyPoly.append( + // ::basegfx::utils::applyLineDashing( aPolyPoly.getB2DPolygon(i), + // aDashArray ) ); + } + + aPolyPoly = aDashedPolyPoly; + } + + ::basegfx::B2DSize aLinePixelSize(strokeAttributes.StrokeWidth, + strokeAttributes.StrokeWidth); + aLinePixelSize *= aMatrix; + ::basegfx::B2DPolyPolygon aStrokedPolyPoly; + if( aLinePixelSize.getLength() < 1.42 ) + { + // line width < 1.0 in device pixel, thus, output as a + // simple hairline poly-polygon + setupOutDevState( viewState, renderState, LINE_COLOR ); + + aStrokedPolyPoly = aPolyPoly; + } + else + { + // render as a 'thick' line + setupOutDevState( viewState, renderState, FILL_COLOR ); + + for( sal_uInt32 i=0; i<aPolyPoly.count(); ++i ) + { + double fMiterMinimumAngle; + if (strokeAttributes.MiterLimit <= 1.0) + { + fMiterMinimumAngle = M_PI_2; + } + else + { + fMiterMinimumAngle = 2.0 * asin(1.0/strokeAttributes.MiterLimit); + } + + // TODO(F2): Also use Cap settings from + // StrokeAttributes, the + // createAreaGeometryForLineStartEnd() method does not + // seem to fit very well here + + // AW: New interface, will create bezier polygons now + aStrokedPolyPoly.append(basegfx::utils::createAreaGeometry( + aPolyPoly.getB2DPolygon(i), + strokeAttributes.StrokeWidth*0.5, + b2DJoineFromJoin(strokeAttributes.JoinType), + unoCapeFromCap(strokeAttributes.StartCapType), + basegfx::deg2rad(12.5) /* default fMaxAllowedAngle*/ , + 0.4 /* default fMaxPartOfEdge*/ , + fMiterMinimumAngle + )); + //aStrokedPolyPoly.append( + // ::basegfx::utils::createAreaGeometryForPolygon( aPolyPoly.getB2DPolygon(i), + // strokeAttributes.StrokeWidth*0.5, + // b2DJoineFromJoin(strokeAttributes.JoinType) ) ); + } + } + + // transform only _now_, all the StrokeAttributes are in + // user coordinates. + aStrokedPolyPoly.transform( aMatrix ); + + // TODO(F2): When using alpha here, must handle that via + // temporary surface or somesuch. + + // Note: the generated stroke poly-polygon is NOT free of + // self-intersections. Therefore, if we would render it + // via OutDev::DrawPolyPolygon(), on/off fill would + // generate off areas on those self-intersections. + for( sal_uInt32 i=0; i<aStrokedPolyPoly.count(); ++i ) + { + const basegfx::B2DPolygon& polygon = aStrokedPolyPoly.getB2DPolygon( i ); + if( polygon.isClosed()) { + mpOutDevProvider->getOutDev().DrawPolygon( polygon ); + if( mp2ndOutDevProvider ) + mp2ndOutDevProvider->getOutDev().DrawPolygon( polygon ); + } else { + mpOutDevProvider->getOutDev().DrawPolyLine( polygon ); + if( mp2ndOutDevProvider ) + mp2ndOutDevProvider->getOutDev().DrawPolyLine( polygon ); + } + } + } + + // TODO(P1): Provide caching here. + return uno::Reference< rendering::XCachedPrimitive >(nullptr); + } + + uno::Reference< rendering::XCachedPrimitive > CanvasHelper::strokeTexturedPolyPolygon( const rendering::XCanvas* , + const uno::Reference< rendering::XPolyPolygon2D >& , + const rendering::ViewState& , + const rendering::RenderState& , + const uno::Sequence< rendering::Texture >& , + const rendering::StrokeAttributes& ) + { + return uno::Reference< rendering::XCachedPrimitive >(nullptr); + } + + uno::Reference< rendering::XCachedPrimitive > CanvasHelper::strokeTextureMappedPolyPolygon( const rendering::XCanvas* , + const uno::Reference< rendering::XPolyPolygon2D >& , + const rendering::ViewState& , + const rendering::RenderState& , + const uno::Sequence< rendering::Texture >& , + const uno::Reference< geometry::XMapping2D >& , + const rendering::StrokeAttributes& ) + { + return uno::Reference< rendering::XCachedPrimitive >(nullptr); + } + + uno::Reference< rendering::XPolyPolygon2D > CanvasHelper::queryStrokeShapes( const rendering::XCanvas* , + const uno::Reference< rendering::XPolyPolygon2D >& , + const rendering::ViewState& , + const rendering::RenderState& , + const rendering::StrokeAttributes& ) + { + return uno::Reference< rendering::XPolyPolygon2D >(nullptr); + } + + uno::Reference< rendering::XCachedPrimitive > CanvasHelper::fillPolyPolygon( const rendering::XCanvas* , + const uno::Reference< rendering::XPolyPolygon2D >& xPolyPolygon, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState ) + { + ENSURE_ARG_OR_THROW( xPolyPolygon.is(), + "polygon is NULL"); + + if( mpOutDevProvider ) + { + tools::OutDevStateKeeper aStateKeeper( mpProtectedOutDevProvider ); + + const int nAlpha( setupOutDevState( viewState, renderState, FILL_COLOR ) ); + ::basegfx::B2DPolyPolygon aB2DPolyPoly( + ::basegfx::unotools::b2DPolyPolygonFromXPolyPolygon2D(xPolyPolygon)); + aB2DPolyPoly.setClosed(true); // ensure closed poly, otherwise VCL does not fill + const ::tools::PolyPolygon aPolyPoly( tools::mapPolyPolygon( + aB2DPolyPoly, + viewState, renderState ) ); + const bool bSourceAlpha( renderState.CompositeOperation == rendering::CompositeOperation::SOURCE ); + if( nAlpha == 255 || bSourceAlpha ) + { + mpOutDevProvider->getOutDev().DrawPolyPolygon( aPolyPoly ); + } + else + { + const int nTransPercent( ((255 - nAlpha) * 100 + 128) / 255 ); // normal rounding, no truncation here + mpOutDevProvider->getOutDev().DrawTransparent( aPolyPoly, static_cast<sal_uInt16>(nTransPercent) ); + } + + if( mp2ndOutDevProvider ) + { + // HACK. Normally, CanvasHelper does not care about + // actually what mp2ndOutDev is... well, here we do & + // assume a 1bpp target - everything beyond 97% + // transparency is fully transparent + if( nAlpha > 2 ) + { + mp2ndOutDevProvider->getOutDev().SetFillColor( COL_BLACK ); + mp2ndOutDevProvider->getOutDev().DrawPolyPolygon( aPolyPoly ); + } + } + } + + // TODO(P1): Provide caching here. + return uno::Reference< rendering::XCachedPrimitive >(nullptr); + } + + uno::Reference< rendering::XCachedPrimitive > CanvasHelper::fillTextureMappedPolyPolygon( const rendering::XCanvas* , + const uno::Reference< rendering::XPolyPolygon2D >& , + const rendering::ViewState& , + const rendering::RenderState& , + const uno::Sequence< rendering::Texture >& , + const uno::Reference< geometry::XMapping2D >& ) + { + return uno::Reference< rendering::XCachedPrimitive >(nullptr); + } + + uno::Reference< rendering::XCanvasFont > CanvasHelper::createFont( const rendering::XCanvas* , + const rendering::FontRequest& fontRequest, + const uno::Sequence< beans::PropertyValue >& extraFontProperties, + const geometry::Matrix2D& fontMatrix ) + { + if( mpOutDevProvider && mpDevice ) + { + // TODO(F2): font properties and font matrix + return uno::Reference< rendering::XCanvasFont >( + new CanvasFont(fontRequest, extraFontProperties, fontMatrix, + *mpDevice, mpOutDevProvider) ); + } + + return uno::Reference< rendering::XCanvasFont >(); + } + + uno::Sequence< rendering::FontInfo > CanvasHelper::queryAvailableFonts( const rendering::XCanvas* , + const rendering::FontInfo& , + const uno::Sequence< beans::PropertyValue >& ) + { + // TODO(F2) + return uno::Sequence< rendering::FontInfo >(); + } + + uno::Reference< rendering::XCachedPrimitive > CanvasHelper::drawText( const rendering::XCanvas* , + const rendering::StringContext& text, + const uno::Reference< rendering::XCanvasFont >& xFont, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState, + sal_Int8 textDirection ) + { + ENSURE_ARG_OR_THROW( xFont.is(), + "font is NULL"); + + if( mpOutDevProvider ) + { + tools::OutDevStateKeeper aStateKeeper( mpProtectedOutDevProvider ); + + ::Point aOutpos; + if( !setupTextOutput( aOutpos, viewState, renderState, xFont ) ) + return uno::Reference< rendering::XCachedPrimitive >(nullptr); // no output necessary + + // change text direction and layout mode + vcl::text::ComplexTextLayoutFlags nLayoutMode(vcl::text::ComplexTextLayoutFlags::Default); + switch( textDirection ) + { + case rendering::TextDirection::WEAK_LEFT_TO_RIGHT: + case rendering::TextDirection::STRONG_LEFT_TO_RIGHT: + nLayoutMode |= vcl::text::ComplexTextLayoutFlags::BiDiStrong; + nLayoutMode |= vcl::text::ComplexTextLayoutFlags::TextOriginLeft; + break; + + case rendering::TextDirection::WEAK_RIGHT_TO_LEFT: + nLayoutMode |= vcl::text::ComplexTextLayoutFlags::BiDiRtl; + [[fallthrough]]; + case rendering::TextDirection::STRONG_RIGHT_TO_LEFT: + nLayoutMode |= vcl::text::ComplexTextLayoutFlags::BiDiRtl | vcl::text::ComplexTextLayoutFlags::BiDiStrong; + nLayoutMode |= vcl::text::ComplexTextLayoutFlags::TextOriginRight; + break; + } + + // TODO(F2): alpha + mpOutDevProvider->getOutDev().SetLayoutMode( nLayoutMode ); + mpOutDevProvider->getOutDev().DrawText( aOutpos, + text.Text, + ::canvas::tools::numeric_cast<sal_uInt16>(text.StartPosition), + ::canvas::tools::numeric_cast<sal_uInt16>(text.Length) ); + + if( mp2ndOutDevProvider ) + { + mp2ndOutDevProvider->getOutDev().SetLayoutMode( nLayoutMode ); + mp2ndOutDevProvider->getOutDev().DrawText( aOutpos, + text.Text, + ::canvas::tools::numeric_cast<sal_uInt16>(text.StartPosition), + ::canvas::tools::numeric_cast<sal_uInt16>(text.Length) ); + } + } + + return uno::Reference< rendering::XCachedPrimitive >(nullptr); + } + + uno::Reference< rendering::XCachedPrimitive > CanvasHelper::drawTextLayout( const rendering::XCanvas* , + const uno::Reference< rendering::XTextLayout >& xLayoutedText, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState ) + { + ENSURE_ARG_OR_THROW( xLayoutedText.is(), + "layout is NULL"); + + TextLayout* pTextLayout = dynamic_cast< TextLayout* >( xLayoutedText.get() ); + + if( pTextLayout ) + { + if( mpOutDevProvider ) + { + tools::OutDevStateKeeper aStateKeeper( mpProtectedOutDevProvider ); + + // TODO(T3): Race condition. We're taking the font + // from xLayoutedText, and then calling draw() at it, + // without exclusive access. Move setupTextOutput(), + // e.g. to impltools? + + ::Point aOutpos; + if( !setupTextOutput( aOutpos, viewState, renderState, xLayoutedText->getFont() ) ) + return uno::Reference< rendering::XCachedPrimitive >(nullptr); // no output necessary + + // TODO(F2): What about the offset scalings? + // TODO(F2): alpha + pTextLayout->draw( mpOutDevProvider->getOutDev(), aOutpos, viewState, renderState ); + + if( mp2ndOutDevProvider ) + pTextLayout->draw( mp2ndOutDevProvider->getOutDev(), aOutpos, viewState, renderState ); + } + } + else + { + ENSURE_ARG_OR_THROW( false, + "TextLayout not compatible with this canvas" ); + } + + return uno::Reference< rendering::XCachedPrimitive >(nullptr); + } + + uno::Reference< rendering::XCachedPrimitive > CanvasHelper::implDrawBitmap( const rendering::XCanvas* pCanvas, + const uno::Reference< rendering::XBitmap >& xBitmap, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState, + bool bModulateColors ) + { + ENSURE_ARG_OR_THROW( xBitmap.is(), + "bitmap is NULL"); + + ::canvas::tools::verifyInput( renderState, + __func__, + mpDevice, + 4, + bModulateColors ? 3 : 0 ); + + if( mpOutDevProvider ) + { + tools::OutDevStateKeeper aStateKeeper( mpProtectedOutDevProvider ); + setupOutDevState( viewState, renderState, IGNORE_COLOR ); + + ::basegfx::B2DHomMatrix aMatrix; + ::canvas::tools::mergeViewAndRenderTransform(aMatrix, viewState, renderState); + + ::basegfx::B2DPoint aOutputPos( 0.0, 0.0 ); + aOutputPos *= aMatrix; + + BitmapEx aBmpEx( tools::bitmapExFromXBitmap(xBitmap) ); + + // TODO(F2): Implement modulation again for other color + // channels (currently, works only for alpha). Note: this + // is already implemented in transformBitmap() + if( bModulateColors && + renderState.DeviceColor.getLength() > 3 ) + { + // optimize away the case where alpha modulation value + // is 1.0 - we then simply switch off modulation at all + bModulateColors = !::rtl::math::approxEqual( + renderState.DeviceColor[3], 1.0); + } + + // check whether we can render bitmap as-is: must not + // modulate colors, matrix must either be the identity + // transform (that's clear), _or_ contain only + // translational components. + if( !bModulateColors && + (aMatrix.isIdentity() || + (::basegfx::fTools::equalZero( aMatrix.get(0,1) ) && + ::basegfx::fTools::equalZero( aMatrix.get(1,0) ) && + ::rtl::math::approxEqual(aMatrix.get(0,0), 1.0) && + ::rtl::math::approxEqual(aMatrix.get(1,1), 1.0)) ) ) + { + // optimized case: identity matrix, or only + // translational components. + mpOutDevProvider->getOutDev().DrawBitmapEx( vcl::unotools::pointFromB2DPoint( aOutputPos ), + aBmpEx ); + + if( mp2ndOutDevProvider ) + { + // HACK. Normally, CanvasHelper does not care about + // actually what mp2ndOutDev is... well, here we do & + // assume a 1bpp target - everything beyond 97% + // transparency is fully transparent + if( aBmpEx.IsAlpha() && !SkiaHelper::isVCLSkiaEnabled()) + { + BitmapFilter::Filter(aBmpEx, BitmapAlphaClampFilter(253)); + } + + mp2ndOutDevProvider->getOutDev().DrawBitmapEx( vcl::unotools::pointFromB2DPoint( aOutputPos ), + aBmpEx ); + } + + // Returning a cache object is not useful, the XBitmap + // itself serves this purpose + return uno::Reference< rendering::XCachedPrimitive >(nullptr); + } + else if( mpOutDevProvider->getOutDev().HasFastDrawTransformedBitmap()) + { + ::basegfx::B2DHomMatrix aSizeTransform; + aSizeTransform.scale( aBmpEx.GetSizePixel().Width(), aBmpEx.GetSizePixel().Height() ); + aMatrix = aMatrix * aSizeTransform; + const double fAlpha = bModulateColors ? renderState.DeviceColor[3] : 1.0; + + mpOutDevProvider->getOutDev().DrawTransformedBitmapEx( aMatrix, aBmpEx, fAlpha ); + if( mp2ndOutDevProvider ) + { + if( aBmpEx.IsAlpha() ) + { + // tdf#157790 invert alpha mask + // Due to commit 81994cb2b8b32453a92bcb011830fcb884f22ff3, + // the alpha mask needs to be inverted. Note: when + // testing tdf#157790, this code only gets executed + // when Skia is enabled. + AlphaMask aAlpha( aBmpEx.GetAlphaMask() ); + aAlpha.Invert(); + aBmpEx = BitmapEx( aBmpEx.GetBitmap(), aAlpha ); + + // HACK. Normally, CanvasHelper does not care about + // actually what mp2ndOutDev is... well, here we do & + // assume a 1bpp target - everything beyond 97% + // transparency is fully transparent + if( !SkiaHelper::isVCLSkiaEnabled()) + { + BitmapFilter::Filter(aBmpEx, BitmapAlphaClampFilter(253)); + } + } + + mp2ndOutDevProvider->getOutDev().DrawTransformedBitmapEx( aMatrix, aBmpEx ); + } + return uno::Reference< rendering::XCachedPrimitive >(nullptr); + } + else + { + // Matrix contains non-trivial transformation (or + // color modulation is requested), decompose to check + // whether GraphicObject suffices + ::basegfx::B2DVector aScale; + double nRotate; + double nShearX; + aMatrix.decompose( aScale, aOutputPos, nRotate, nShearX ); + + GraphicAttr aGrfAttr; + GraphicObjectSharedPtr pGrfObj; + + ::Size aBmpSize( aBmpEx.GetSizePixel() ); + + // setup alpha modulation + if( bModulateColors ) + { + const double nAlphaModulation( renderState.DeviceColor[3] ); + + // TODO(F1): Note that the GraphicManager has a + // subtle difference in how it calculates the + // resulting alpha value: it's using the inverse + // alpha values (i.e. 'transparency'), and + // calculates transOrig + transModulate, instead + // of transOrig + transModulate - + // transOrig*transModulate (which would be + // equivalent to the origAlpha*modulateAlpha the + // DX canvas performs) + aGrfAttr.SetAlpha( + static_cast< sal_uInt8 >( + ::basegfx::fround( 255.0 * nAlphaModulation ) ) ); + } + + if( ::basegfx::fTools::equalZero( nShearX ) ) + { + // no shear, GraphicObject is enough (the + // GraphicObject only supports scaling, rotation + // and translation) + + // #i75339# don't apply mirror flags, having + // negative size values is enough to make + // GraphicObject flip the bitmap + + // The angle has to be mapped from radian to tenths of + // degrees with the orientation reversed: [0,2Pi) -> + // (3600,0]. Note that the original angle may have + // values outside the [0,2Pi) interval. + const double nAngleInTenthOfDegrees (3600.0 - basegfx::rad2deg<10>(nRotate)); + aGrfAttr.SetRotation( Degree10(::basegfx::fround(nAngleInTenthOfDegrees)) ); + + pGrfObj = std::make_shared<GraphicObject>( aBmpEx ); + } + else + { + // modify output position, to account for the fact + // that transformBitmap() always normalizes its output + // bitmap into the smallest enclosing box. + ::basegfx::B2DRectangle aDestRect; + ::canvas::tools::calcTransformedRectBounds( aDestRect, + ::basegfx::B2DRectangle(0, + 0, + aBmpSize.Width(), + aBmpSize.Height()), + aMatrix ); + + aOutputPos.setX( aDestRect.getMinX() ); + aOutputPos.setY( aDestRect.getMinY() ); + + // complex transformation, use generic affine bitmap + // transformation + aBmpEx = tools::transformBitmap( aBmpEx, + aMatrix ); + + pGrfObj = std::make_shared<GraphicObject>( aBmpEx ); + + // clear scale values, generated bitmap already + // contains scaling + aScale.setX( 1.0 ); aScale.setY( 1.0 ); + + // update bitmap size, bitmap has changed above. + aBmpSize = aBmpEx.GetSizePixel(); + } + + // output GraphicObject + const ::Point aPt( vcl::unotools::pointFromB2DPoint( aOutputPos ) ); + const ::Size aSz( ::basegfx::fround( aScale.getX() * aBmpSize.Width() ), + ::basegfx::fround( aScale.getY() * aBmpSize.Height() ) ); + + pGrfObj->Draw(mpOutDevProvider->getOutDev(), + aPt, + aSz, + &aGrfAttr); + + if( mp2ndOutDevProvider ) + { + GraphicObjectSharedPtr p2ndGrfObj = pGrfObj; + if( aBmpEx.IsAlpha() ) + { + // tdf#157790 invert alpha mask + // Due to commit 81994cb2b8b32453a92bcb011830fcb884f22ff3, + // the alpha mask needs to be inverted. Note: when + // testing tdf#157790, this code only gets executed + // when Skia is disabled. + AlphaMask aAlpha( aBmpEx.GetAlphaMask() ); + aAlpha.Invert(); + BitmapEx a2ndBmpEx( aBmpEx.GetBitmap(), aAlpha ); + p2ndGrfObj = std::make_shared<GraphicObject>( a2ndBmpEx ); + } + + p2ndGrfObj->Draw(mp2ndOutDevProvider->getOutDev(), + aPt, + aSz, + &aGrfAttr); + } + + // created GraphicObject, which possibly cached + // display bitmap - return cache object, to retain + // that information. + return uno::Reference< rendering::XCachedPrimitive >( + new CachedBitmap( pGrfObj, + aPt, + aSz, + aGrfAttr, + viewState, + renderState, + // cast away const, need to + // change refcount (as this is + // ~invisible to client code, + // still logically const) + const_cast< rendering::XCanvas* >(pCanvas)) ); + } + } + + // Nothing rendered + return uno::Reference< rendering::XCachedPrimitive >(nullptr); + } + + uno::Reference< rendering::XCachedPrimitive > CanvasHelper::drawBitmap( const rendering::XCanvas* pCanvas, + const uno::Reference< rendering::XBitmap >& xBitmap, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState ) + { + return implDrawBitmap( pCanvas, + xBitmap, + viewState, + renderState, + false ); + } + + uno::Reference< rendering::XCachedPrimitive > CanvasHelper::drawBitmapModulated( const rendering::XCanvas* pCanvas, + const uno::Reference< rendering::XBitmap >& xBitmap, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState ) + { + return implDrawBitmap( pCanvas, + xBitmap, + viewState, + renderState, + true ); + } + + geometry::IntegerSize2D CanvasHelper::getSize() + { + if( !mpOutDevProvider ) + return geometry::IntegerSize2D(); // we're disposed + + return vcl::unotools::integerSize2DFromSize( mpOutDevProvider->getOutDev().GetOutputSizePixel() ); + } + + uno::Reference< rendering::XBitmap > CanvasHelper::getScaledBitmap( const geometry::RealSize2D& newSize, + bool beFast ) + { + if( !mpOutDevProvider || !mpDevice ) + return uno::Reference< rendering::XBitmap >(); // we're disposed + + OutputDevice& rOutDev( mpOutDevProvider->getOutDev() ); + + tools::OutDevStateKeeper aStateKeeper( mpProtectedOutDevProvider ); + rOutDev.EnableMapMode( false ); + rOutDev.SetAntialiasing( AntialiasingFlags::Enable ); + + // TODO(F2): Support alpha vdev canvas here + const Point aEmptyPoint(0,0); + const Size aBmpSize( rOutDev.GetOutputSizePixel() ); + + BitmapEx aBitmap( rOutDev.GetBitmapEx(aEmptyPoint, aBmpSize) ); + + aBitmap.Scale( vcl::unotools::sizeFromRealSize2D(newSize), + beFast ? BmpScaleFlag::Default : BmpScaleFlag::BestQuality ); + + return uno::Reference< rendering::XBitmap >( + new CanvasBitmap( aBitmap, *mpDevice, mpOutDevProvider ) ); + } + + uno::Sequence< sal_Int8 > CanvasHelper::getData( rendering::IntegerBitmapLayout& rLayout, + const geometry::IntegerRectangle2D& rect ) + { + if( !mpOutDevProvider ) + return uno::Sequence< sal_Int8 >(); // we're disposed + + rLayout = getMemoryLayout(); + + // TODO(F2): Support alpha canvas here + const ::tools::Rectangle aRect( vcl::unotools::rectangleFromIntegerRectangle2D(rect) ); + + OutputDevice& rOutDev( mpOutDevProvider->getOutDev() ); + + tools::OutDevStateKeeper aStateKeeper( mpProtectedOutDevProvider ); + rOutDev.EnableMapMode( false ); + rOutDev.SetAntialiasing( AntialiasingFlags::Enable ); + + Bitmap aBitmap( rOutDev.GetBitmapEx(aRect.TopLeft(), + aRect.GetSize()).GetBitmap() ); + + BitmapScopedReadAccess pReadAccess( aBitmap ); + + ENSURE_OR_THROW( pReadAccess.get() != nullptr, + "Could not acquire read access to OutDev bitmap" ); + + const sal_Int32 nWidth( rect.X2 - rect.X1 ); + const sal_Int32 nHeight( rect.Y2 - rect.Y1 ); + + rLayout.ScanLines = nHeight; + rLayout.ScanLineBytes = nWidth*4; + rLayout.ScanLineStride = rLayout.ScanLineBytes; + + uno::Sequence< sal_Int8 > aRes( 4*nWidth*nHeight ); + sal_Int8* pRes = aRes.getArray(); + + int nCurrPos(0); + for( int y=0; y<nHeight; ++y ) + { + for( int x=0; x<nWidth; ++x ) + { + pRes[ nCurrPos++ ] = pReadAccess->GetColor( y, x ).GetRed(); + pRes[ nCurrPos++ ] = pReadAccess->GetColor( y, x ).GetGreen(); + pRes[ nCurrPos++ ] = pReadAccess->GetColor( y, x ).GetBlue(); + pRes[ nCurrPos++ ] = -1; + } + } + + return aRes; + } + + uno::Sequence< sal_Int8 > CanvasHelper::getPixel( rendering::IntegerBitmapLayout& rLayout, + const geometry::IntegerPoint2D& pos ) + { + if( !mpOutDevProvider ) + return uno::Sequence< sal_Int8 >(); // we're disposed + + rLayout = getMemoryLayout(); + rLayout.ScanLines = 1; + rLayout.ScanLineBytes = 4; + rLayout.ScanLineStride = rLayout.ScanLineBytes; + + OutputDevice& rOutDev( mpOutDevProvider->getOutDev() ); + + tools::OutDevStateKeeper aStateKeeper( mpProtectedOutDevProvider ); + rOutDev.EnableMapMode( false ); + rOutDev.SetAntialiasing( AntialiasingFlags::Enable ); + + const Size aBmpSize( rOutDev.GetOutputSizePixel() ); + + ENSURE_ARG_OR_THROW( pos.X >= 0 && pos.X < aBmpSize.Width(), + "X coordinate out of bounds" ); + ENSURE_ARG_OR_THROW( pos.Y >= 0 && pos.Y < aBmpSize.Height(), + "Y coordinate out of bounds" ); + + // TODO(F2): Support alpha canvas here + return ::canvas::tools::colorToStdIntSequence( + rOutDev.GetPixel( + vcl::unotools::pointFromIntegerPoint2D( pos ))); + } + + rendering::IntegerBitmapLayout CanvasHelper::getMemoryLayout() + { + if( !mpOutDevProvider ) + return rendering::IntegerBitmapLayout(); // we're disposed + + rendering::IntegerBitmapLayout aBitmapLayout( ::canvas::tools::getStdMemoryLayout(getSize()) ); + if ( !mbHaveAlpha ) + aBitmapLayout.ColorSpace = canvas::tools::getStdColorSpaceWithoutAlpha(); + + return aBitmapLayout; + } + + int CanvasHelper::setupOutDevState( const rendering::ViewState& viewState, + const rendering::RenderState& renderState, + ColorType eColorType ) const + { + ENSURE_OR_THROW( mpOutDevProvider, + "outdev null. Are we disposed?" ); + + ::canvas::tools::verifyInput( renderState, + __func__, + mpDevice, + 2, + eColorType == IGNORE_COLOR ? 0 : 3 ); + + OutputDevice& rOutDev( mpOutDevProvider->getOutDev() ); + OutputDevice* p2ndOutDev = nullptr; + + rOutDev.EnableMapMode( false ); + rOutDev.SetAntialiasing( AntialiasingFlags::Enable ); + + if( mp2ndOutDevProvider ) + p2ndOutDev = &mp2ndOutDevProvider->getOutDev(); + + int nAlpha(255); + + // TODO(P2): Don't change clipping all the time, maintain current clip + // state and change only when update is necessary + ::canvas::tools::clipOutDev(viewState, renderState, rOutDev, p2ndOutDev); + + Color aColor( COL_WHITE ); + + if( renderState.DeviceColor.getLength() > 2 ) + { + aColor = vcl::unotools::stdColorSpaceSequenceToColor( + renderState.DeviceColor ); + } + + // extract alpha, and make color opaque + // afterwards. Otherwise, OutputDevice won't draw anything + nAlpha = aColor.GetAlpha(); + aColor.SetAlpha(255); + + if( eColorType != IGNORE_COLOR ) + { + switch( eColorType ) + { + case LINE_COLOR: + rOutDev.SetLineColor( aColor ); + rOutDev.SetFillColor(); + + if( p2ndOutDev ) + { + p2ndOutDev->SetLineColor( aColor ); + p2ndOutDev->SetFillColor(); + } + break; + + case FILL_COLOR: + rOutDev.SetFillColor( aColor ); + rOutDev.SetLineColor(); + + if( p2ndOutDev ) + { + p2ndOutDev->SetFillColor( aColor ); + p2ndOutDev->SetLineColor(); + } + break; + + case TEXT_COLOR: + rOutDev.SetTextColor( aColor ); + + if( p2ndOutDev ) + p2ndOutDev->SetTextColor( aColor ); + break; + + default: + ENSURE_OR_THROW( false, + "Unexpected color type"); + break; + } + } + + return nAlpha; + } + + bool CanvasHelper::setupTextOutput( ::Point& o_rOutPos, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState, + const uno::Reference< rendering::XCanvasFont >& xFont ) const + { + ENSURE_OR_THROW( mpOutDevProvider, + "outdev null. Are we disposed?" ); + + OutputDevice& rOutDev( mpOutDevProvider->getOutDev() ); + + setupOutDevState( viewState, renderState, TEXT_COLOR ); + + CanvasFont* pFont = dynamic_cast< CanvasFont* >( xFont.get() ); + + ENSURE_ARG_OR_THROW( pFont, + "Font not compatible with this canvas" ); + + vcl::Font aVCLFont = pFont->getVCLFont(); + + Color aColor( COL_BLACK ); + + if( renderState.DeviceColor.getLength() > 2 ) + { + aColor = vcl::unotools::stdColorSpaceSequenceToColor( + renderState.DeviceColor ); + } + + // setup font color + aVCLFont.SetColor( aColor ); + aVCLFont.SetFillColor( aColor ); + + // no need to replicate this for mp2ndOutDev, we're modifying only aVCLFont here. + if( !tools::setupFontTransform( o_rOutPos, aVCLFont, viewState, renderState, rOutDev ) ) + return false; + + rOutDev.SetFont( aVCLFont ); + + if( mp2ndOutDevProvider ) + mp2ndOutDevProvider->getOutDev().SetFont( aVCLFont ); + + return true; + } + + bool CanvasHelper::repaint( const GraphicObjectSharedPtr& rGrf, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState, + const ::Point& rPt, + const ::Size& rSz, + const GraphicAttr& rAttr ) const + { + ENSURE_OR_RETURN_FALSE( rGrf, + "Invalid Graphic" ); + + if( !mpOutDevProvider ) + return false; // disposed + else + { + tools::OutDevStateKeeper aStateKeeper( mpProtectedOutDevProvider ); + setupOutDevState( viewState, renderState, IGNORE_COLOR ); + + if (!rGrf->Draw(mpOutDevProvider->getOutDev(), rPt, rSz, &rAttr)) + return false; + + // #i80779# Redraw also into mask outdev + if (mp2ndOutDevProvider) + return rGrf->Draw(mp2ndOutDevProvider->getOutDev(), rPt, rSz, &rAttr); + + return true; + } + } + + void CanvasHelper::flush() const + { + if (mpOutDevProvider) + mpOutDevProvider->getOutDev().Flush(); + + if (mp2ndOutDevProvider) + mp2ndOutDevProvider->getOutDev().Flush(); + } + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/vcl/canvashelper.hxx b/canvas/source/vcl/canvashelper.hxx new file mode 100644 index 0000000000..ba385110bf --- /dev/null +++ b/canvas/source/vcl/canvashelper.hxx @@ -0,0 +1,309 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/geometry/IntegerPoint2D.hpp> +#include <com/sun/star/geometry/IntegerRectangle2D.hpp> +#include <com/sun/star/rendering/IntegerBitmapLayout.hpp> +#include <com/sun/star/rendering/XCanvas.hpp> + +#include "cachedbitmap.hxx" +#include "outdevprovider.hxx" + + +namespace vclcanvas +{ + class SpriteCanvas; + + /** Helper class for basic canvas functionality. Also offers + optional backbuffer painting, when providing it with a second + OutputDevice to render into. + */ + class CanvasHelper + { + public: + /** Create canvas helper + */ + CanvasHelper(); + + /// make noncopyable + CanvasHelper(const CanvasHelper&) = delete; + const CanvasHelper& operator=(const CanvasHelper&) = delete; + + /// Release all references + void disposing(); + + /** Initialize canvas helper + + This method late-initializes the canvas helper, providing + it with the necessary device and output objects. Note that + the CanvasHelper does <em>not</em> take ownership of the + passed rDevice reference, nor does it perform any + reference counting. Thus, to prevent the reference counted + SpriteCanvas object from deletion, the user of this class + is responsible for holding ref-counted references itself! + + @param rDevice + Reference device this canvas is associated with + + @param rOutDev + Set primary output device for this canvas. That's where + all content is output to. + + @param bProtect + When true, all output operations preserve outdev + state. When false, outdev state might change at any time. + + @param bHaveAlpha + When true, hasAlpha() will always return true, otherwise, false. + */ + void init( css::rendering::XGraphicDevice& rDevice, + const OutDevProviderSharedPtr& rOutDev, + bool bProtect, + bool bHaveAlpha ); + + /** Set primary output device + + This changes the primary output device, where rendering is + sent to. + */ + void setOutDev( const OutDevProviderSharedPtr& rOutDev, + bool bProtect); + + /** Set secondary output device + + Used for sprites, to generate mask bitmap. + */ + void setBackgroundOutDev( const OutDevProviderSharedPtr& rOutDev ); + + + // CanvasHelper functionality + // ========================== + + // XCanvas (only providing, not implementing the + // interface. Also note subtle method parameter differences) + void clear(); + void drawLine( const css::rendering::XCanvas* rCanvas, + const css::geometry::RealPoint2D& aStartPoint, + const css::geometry::RealPoint2D& aEndPoint, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState ); + void drawBezier( const css::rendering::XCanvas* rCanvas, + const css::geometry::RealBezierSegment2D& aBezierSegment, + const css::geometry::RealPoint2D& aEndPoint, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState ); + css::uno::Reference< css::rendering::XCachedPrimitive > + drawPolyPolygon( const css::rendering::XCanvas* rCanvas, + const css::uno::Reference< + css::rendering::XPolyPolygon2D >& xPolyPolygon, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState ); + css::uno::Reference< css::rendering::XCachedPrimitive > + strokePolyPolygon( const css::rendering::XCanvas* rCanvas, + const css::uno::Reference< + css::rendering::XPolyPolygon2D >& xPolyPolygon, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState, + const css::rendering::StrokeAttributes& strokeAttributes ); + css::uno::Reference< css::rendering::XCachedPrimitive > + strokeTexturedPolyPolygon( const css::rendering::XCanvas* rCanvas, + const css::uno::Reference< + css::rendering::XPolyPolygon2D >& xPolyPolygon, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState, + const css::uno::Sequence< + css::rendering::Texture >& textures, + const css::rendering::StrokeAttributes& strokeAttributes ); + css::uno::Reference< css::rendering::XCachedPrimitive > + strokeTextureMappedPolyPolygon( const css::rendering::XCanvas* rCanvas, + const css::uno::Reference< + css::rendering::XPolyPolygon2D >& xPolyPolygon, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState, + const css::uno::Sequence< + css::rendering::Texture >& textures, + const css::uno::Reference< + css::geometry::XMapping2D >& xMapping, + const css::rendering::StrokeAttributes& strokeAttributes ); + css::uno::Reference< css::rendering::XPolyPolygon2D > + queryStrokeShapes( const css::rendering::XCanvas* rCanvas, + const css::uno::Reference< + css::rendering::XPolyPolygon2D >& xPolyPolygon, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState, + const css::rendering::StrokeAttributes& strokeAttributes ); + css::uno::Reference< css::rendering::XCachedPrimitive > + fillPolyPolygon( const css::rendering::XCanvas* rCanvas, + const css::uno::Reference< + css::rendering::XPolyPolygon2D >& xPolyPolygon, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState ); + css::uno::Reference< css::rendering::XCachedPrimitive > + fillTexturedPolyPolygon( const css::rendering::XCanvas* rCanvas, + const css::uno::Reference< + css::rendering::XPolyPolygon2D >& xPolyPolygon, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState, + const css::uno::Sequence< + css::rendering::Texture >& textures ); + css::uno::Reference< css::rendering::XCachedPrimitive > + fillTextureMappedPolyPolygon( const css::rendering::XCanvas* rCanvas, + const css::uno::Reference< + css::rendering::XPolyPolygon2D >& xPolyPolygon, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState, + const css::uno::Sequence< + css::rendering::Texture >& textures, + const css::uno::Reference< + css::geometry::XMapping2D >& xMapping ); + + css::uno::Reference< css::rendering::XCanvasFont > + createFont( const css::rendering::XCanvas* rCanvas, + const css::rendering::FontRequest& fontRequest, + const css::uno::Sequence< + css::beans::PropertyValue >& extraFontProperties, + const css::geometry::Matrix2D& fontMatrix ); + + css::uno::Sequence< css::rendering::FontInfo > + queryAvailableFonts( const css::rendering::XCanvas* rCanvas, + const css::rendering::FontInfo& aFilter, + const css::uno::Sequence< + css::beans::PropertyValue >& aFontProperties ); + + css::uno::Reference< css::rendering::XCachedPrimitive > + drawText( const css::rendering::XCanvas* rCanvas, + const css::rendering::StringContext& text, + const css::uno::Reference< + css::rendering::XCanvasFont >& xFont, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState, + sal_Int8 textDirection ); + + css::uno::Reference< css::rendering::XCachedPrimitive > + drawTextLayout( const css::rendering::XCanvas* rCanvas, + const css::uno::Reference< + css::rendering::XTextLayout >& laidOutText, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState ); + + css::uno::Reference< css::rendering::XCachedPrimitive > + drawBitmap( const css::rendering::XCanvas* rCanvas, + const css::uno::Reference< + css::rendering::XBitmap >& xBitmap, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState ); + css::uno::Reference< css::rendering::XCachedPrimitive > + drawBitmapModulated( const css::rendering::XCanvas* rCanvas, + const css::uno::Reference< + css::rendering::XBitmap >& xBitmap, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState ); + // cast away const, need to change refcount (as this is + // ~invisible to client code, still logically const) + css::uno::Reference< css::rendering::XGraphicDevice > + getDevice() { return css::uno::Reference< css::rendering::XGraphicDevice >(mpDevice); } + + + // BitmapCanvasHelper functionality + // ================================ + + css::geometry::IntegerSize2D getSize(); + + css::uno::Reference< css::rendering::XBitmap > + getScaledBitmap( const css::geometry::RealSize2D& newSize, + bool beFast ); + + css::uno::Sequence< sal_Int8 > + getData( css::rendering::IntegerBitmapLayout& bitmapLayout, + const css::geometry::IntegerRectangle2D& rect ); + + css::uno::Sequence< sal_Int8 > + getPixel( css::rendering::IntegerBitmapLayout& bitmapLayout, + const css::geometry::IntegerPoint2D& pos ); + + css::rendering::IntegerBitmapLayout getMemoryLayout(); + + /// Repaint a cached bitmap + bool repaint( const GraphicObjectSharedPtr& rGrf, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState, + const ::Point& rPt, + const ::Size& rSz, + const GraphicAttr& rAttr ) const; + + /** Flush drawing queue. + + This only works for Window canvases, and ensures that all + pending render operations are flushed to the + driver/hardware. + */ + void flush() const; + + enum ColorType + { + LINE_COLOR, FILL_COLOR, TEXT_COLOR, IGNORE_COLOR + }; + + // returns alpha of color + int setupOutDevState( const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState, + ColorType eColorType ) const; + + bool hasAlpha() const { return mbHaveAlpha; } + + protected: + /** Phyical output device + + Deliberately not a refcounted reference, because of + potential circular references for spritecanvas. + */ + css::rendering::XGraphicDevice* mpDevice; + + /// Rendering to this outdev preserves its state + OutDevProviderSharedPtr mpProtectedOutDevProvider; + + /// Rendering to this outdev does not preserve its state + OutDevProviderSharedPtr mpOutDevProvider; + + /// Rendering to this outdev does not preserve its state + OutDevProviderSharedPtr mp2ndOutDevProvider; + + /// When true, content is able to represent alpha + bool mbHaveAlpha; + + private: + css::uno::Reference< css::rendering::XCachedPrimitive > + implDrawBitmap( const css::rendering::XCanvas* rCanvas, + const css::uno::Reference< + css::rendering::XBitmap >& xBitmap, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState, + bool bModulateColors ); + + bool setupTextOutput( ::Point& o_rOutPos, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState, + const css::uno::Reference< css::rendering::XCanvasFont >& xFont ) const; + + }; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/vcl/canvashelper_texturefill.cxx b/canvas/source/vcl/canvashelper_texturefill.cxx new file mode 100644 index 0000000000..5e9399f568 --- /dev/null +++ b/canvas/source/vcl/canvashelper_texturefill.cxx @@ -0,0 +1,1074 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <cstdlib> +#include <tuple> + +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/numeric/ftools.hxx> +#include <basegfx/point/b2dpoint.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/range/b2drectangle.hxx> +#include <basegfx/utils/canvastools.hxx> +#include <basegfx/utils/keystoplerp.hxx> +#include <basegfx/utils/lerp.hxx> +#include <basegfx/utils/tools.hxx> +#include <com/sun/star/rendering/TexturingMode.hpp> +#include <rtl/math.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <tools/poly.hxx> +#include <vcl/bitmapex.hxx> +#include <vcl/canvastools.hxx> +#include <vcl/virdev.hxx> +#include <vcl/gradient.hxx> + +#include <canvas/canvastools.hxx> +#include <parametricpolypolygon.hxx> + +#include "canvashelper.hxx" +#include "impltools.hxx" + + +using namespace ::com::sun::star; + +namespace vclcanvas +{ + namespace + { + bool textureFill( OutputDevice& rOutDev, + const GraphicObject& rGraphic, + const ::Point& rPosPixel, + const ::Size& rNextTileX, + const ::Size& rNextTileY, + sal_Int32 nTilesX, + sal_Int32 nTilesY, + const ::Size& rTileSize, + const GraphicAttr& rAttr) + { + bool bRet( false ); + Point aCurrPos; + int nX, nY; + + for( nY=0; nY < nTilesY; ++nY ) + { + aCurrPos.setX( rPosPixel.X() + nY*rNextTileY.Width() ); + aCurrPos.setY( rPosPixel.Y() + nY*rNextTileY.Height() ); + + for( nX=0; nX < nTilesX; ++nX ) + { + // update return value. This method should return true, if + // at least one of the looped Draws succeeded. + bRet |= rGraphic.Draw(rOutDev, + aCurrPos, + rTileSize, + &rAttr); + + aCurrPos.AdjustX(rNextTileX.Width() ); + aCurrPos.AdjustY(rNextTileX.Height() ); + } + } + + return bRet; + } + + + /** Fill linear or axial gradient + + Since most of the code for linear and axial gradients are + the same, we've a unified method here + */ + void fillLinearGradient( OutputDevice& rOutDev, + const ::basegfx::B2DHomMatrix& rTextureTransform, + const ::tools::Rectangle& rBounds, + unsigned int nStepCount, + const ::canvas::ParametricPolyPolygon::Values& rValues, + const std::vector< ::Color >& rColors ) + { + // determine general position of gradient in relation to + // the bound rect + // ===================================================== + + ::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 *= rTextureTransform; + aLeftBottom *= rTextureTransform; + aRightTop *= rTextureTransform; + aRightBottom*= rTextureTransform; + + // calc length of bound rect diagonal + const ::basegfx::B2DVector aBoundRectDiagonal( + vcl::unotools::b2DPointFromPoint( rBounds.TopLeft() ) - + vcl::unotools::b2DPointFromPoint( rBounds.BottomRight() ) ); + const double nDiagonalLength( aBoundRectDiagonal.getLength() ); + + // create direction of gradient: + // _______ + // | | | + // -> | | | ... + // | | | + // ------- + ::basegfx::B2DVector aDirection( aRightTop - aLeftTop ); + aDirection.normalize(); + + // now, we potentially have to enlarge our gradient area + // atop and below the transformed [0,1]x[0,1] unit rect, + // for the gradient to fill the complete bound rect. + ::basegfx::utils::infiniteLineFromParallelogram( aLeftTop, + aLeftBottom, + aRightTop, + aRightBottom, + vcl::unotools::b2DRectangleFromRectangle(rBounds) ); + + + // render gradient + // =============== + + // First try to use directly VCL's DrawGradient(), as that one is generally + // a better choice than here decomposing to polygons. The VCL API allows + // only 2 colors, but that should generally do. + // Do not use nStepCount, it limits optimized implementations, and it's computed + // by vclcanvas based on number of colors, so it's practically irrelevant. + + // 2 colors and 2 stops (at 0 and 1) is a linear gradient: + if( rColors.size() == 2 && rValues.maStops.size() == 2 && rValues.maStops[0] == 0 && rValues.maStops[1] == 1) + { + // tdf#144073 and tdf#147645: use bounds and angle for gradient + // Passing an expanded, rotated polygon noticeably modifies the + // drawing of the gradient in a slideshow due to moving of the + // starting and ending colors far off the edges of the drawing + // surface. So try another way and set the angle of the + // gradient and draw only the unadjusted bounds. + Gradient vclGradient( css::awt::GradientStyle_LINEAR, rColors[ 0 ], rColors[ 1 ] ); + double fRotate = atan2( aDirection.getY(), aDirection.getX() ); + const double nAngleInTenthOfDegrees = 3600.0 - basegfx::rad2deg<10>( fRotate ) + 900.0; + vclGradient.SetAngle( Degree10( ::basegfx::fround( nAngleInTenthOfDegrees ) ) ); + rOutDev.DrawGradient( rBounds, vclGradient ); + return; + } + // 3 colors with first and last being equal and 3 stops (at 0, 0.5 and 1) is an axial gradient: + if( rColors.size() == 3 && rColors[ 0 ] == rColors[ 2 ] + && rValues.maStops.size() == 3 && rValues.maStops[0] == 0 + && rValues.maStops[1] == 0.5 && rValues.maStops[2] == 1) + { + // tdf#144073 and tdf#147645: use bounds and angle for gradient + // Passing an expanded, rotated polygon noticeably modifies the + // drawing of the gradient in a slideshow due to moving of the + // starting and ending colors far off the edges of the drawing + // surface. So try another way and set the angle of the + // gradient and draw only the unadjusted bounds. + Gradient vclGradient( css::awt::GradientStyle_AXIAL, rColors[ 1 ], rColors[ 0 ] ); + double fRotate = atan2( aDirection.getY(), aDirection.getX() ); + const double nAngleInTenthOfDegrees = 3600.0 - basegfx::rad2deg<10>( fRotate ) + 900.0; + vclGradient.SetAngle( Degree10( ::basegfx::fround( nAngleInTenthOfDegrees ) ) ); + rOutDev.DrawGradient( rBounds, vclGradient ); + return; + } + + // for linear gradients, it's easy to render + // non-overlapping polygons: just split the gradient into + // nStepCount small strips. Prepare the strip now. + + // For performance reasons, we create a temporary VCL + // polygon here, keep it all the way and only change the + // vertex values in the loop below (as ::Polygon is a + // pimpl class, creating one every loop turn would really + // stress the mem allocator) + ::tools::Polygon aTempPoly( static_cast<sal_uInt16>(5) ); + + OSL_ENSURE( nStepCount >= 3, + "fillLinearGradient(): stepcount smaller than 3" ); + + + // fill initial strip (extending two times the bound rect's + // diagonal to the 'left' + + + // calculate left edge, by moving left edge of the + // gradient rect two times the bound rect's diagonal to + // the 'left'. Since we postpone actual rendering into the + // loop below, we set the _right_ edge here, which will be + // readily copied into the left edge in the loop below + const ::basegfx::B2DPoint& rPoint1( aLeftTop - 2.0*nDiagonalLength*aDirection ); + aTempPoly[1] = ::Point( ::basegfx::fround( rPoint1.getX() ), + ::basegfx::fround( rPoint1.getY() ) ); + + const ::basegfx::B2DPoint& rPoint2( aLeftBottom - 2.0*nDiagonalLength*aDirection ); + aTempPoly[2] = ::Point( ::basegfx::fround( rPoint2.getX() ), + ::basegfx::fround( rPoint2.getY() ) ); + + + // iteratively render all other strips + + + // ensure that nStepCount matches color stop parity, to + // have a well-defined middle color e.g. for axial + // gradients. + if( (rColors.size() % 2) != (nStepCount % 2) ) + ++nStepCount; + + rOutDev.SetLineColor(); + + basegfx::utils::KeyStopLerp aLerper(rValues.maStops); + + // only iterate nStepCount-1 steps, as the last strip is + // explicitly painted below + for( unsigned int i=0; i<nStepCount-1; ++i ) + { + std::ptrdiff_t nIndex; + double fAlpha; + std::tie(nIndex,fAlpha)=aLerper.lerp(double(i)/nStepCount); + + rOutDev.SetFillColor( + Color( static_cast<sal_uInt8>(basegfx::utils::lerp(rColors[nIndex].GetRed(),rColors[nIndex+1].GetRed(),fAlpha)), + static_cast<sal_uInt8>(basegfx::utils::lerp(rColors[nIndex].GetGreen(),rColors[nIndex+1].GetGreen(),fAlpha)), + static_cast<sal_uInt8>(basegfx::utils::lerp(rColors[nIndex].GetBlue(),rColors[nIndex+1].GetBlue(),fAlpha)) )); + + // copy right edge of polygon to left edge (and also + // copy the closing point) + aTempPoly[0] = aTempPoly[4] = aTempPoly[1]; + aTempPoly[3] = aTempPoly[2]; + + // calculate new right edge, from interpolating + // between start and end line. Note that i is + // increased by one, to account for the fact that we + // calculate the right border here (whereas the fill + // color is governed by the left edge) + const ::basegfx::B2DPoint& rPoint3( + (nStepCount - i-1)/double(nStepCount)*aLeftTop + + (i+1)/double(nStepCount)*aRightTop ); + aTempPoly[1] = ::Point( ::basegfx::fround( rPoint3.getX() ), + ::basegfx::fround( rPoint3.getY() ) ); + + const ::basegfx::B2DPoint& rPoint4( + (nStepCount - i-1)/double(nStepCount)*aLeftBottom + + (i+1)/double(nStepCount)*aRightBottom ); + aTempPoly[2] = ::Point( ::basegfx::fround( rPoint4.getX() ), + ::basegfx::fround( rPoint4.getY() ) ); + + rOutDev.DrawPolygon( aTempPoly ); + } + + // fill final strip (extending two times the bound rect's + // diagonal to the 'right' + + + // copy right edge of polygon to left edge (and also + // copy the closing point) + aTempPoly[0] = aTempPoly[4] = aTempPoly[1]; + aTempPoly[3] = aTempPoly[2]; + + // calculate new right edge, by moving right edge of the + // gradient rect two times the bound rect's diagonal to + // the 'right'. + const ::basegfx::B2DPoint& rPoint3( aRightTop + 2.0*nDiagonalLength*aDirection ); + aTempPoly[0] = aTempPoly[4] = ::Point( ::basegfx::fround( rPoint3.getX() ), + ::basegfx::fround( rPoint3.getY() ) ); + + const ::basegfx::B2DPoint& rPoint4( aRightBottom + 2.0*nDiagonalLength*aDirection ); + aTempPoly[3] = ::Point( ::basegfx::fround( rPoint4.getX() ), + ::basegfx::fround( rPoint4.getY() ) ); + + rOutDev.SetFillColor( rColors.back() ); + + rOutDev.DrawPolygon( aTempPoly ); + } + + void fillPolygonalGradient( OutputDevice& rOutDev, + const ::basegfx::B2DHomMatrix& rTextureTransform, + const ::tools::Rectangle& rBounds, + unsigned int nStepCount, + const ::canvas::ParametricPolyPolygon::Values& rValues, + const std::vector< ::Color >& rColors ) + { + const ::basegfx::B2DPolygon& rGradientPoly( rValues.maGradientPoly ); + + ENSURE_OR_THROW( rGradientPoly.count() > 2, + "fillPolygonalGradient(): polygon without area given" ); + + // For performance reasons, we create a temporary VCL polygon + // here, keep it all the way and only change the vertex values + // in the loop below (as ::Polygon is a pimpl class, creating + // one every loop turn would really stress the mem allocator) + ::basegfx::B2DPolygon aOuterPoly( rGradientPoly ); + ::basegfx::B2DPolygon aInnerPoly; + + // subdivide polygon _before_ rendering, would otherwise have + // to be performed on every loop turn. + if( aOuterPoly.areControlPointsUsed() ) + aOuterPoly = ::basegfx::utils::adaptiveSubdivideByAngle(aOuterPoly); + + aInnerPoly = aOuterPoly; + + // only transform outer polygon _after_ copying it into + // aInnerPoly, because inner polygon has to be scaled before + // the actual texture transformation takes place + aOuterPoly.transform( rTextureTransform ); + + // determine overall transformation for inner polygon (might + // have to be prefixed by anisotrophic scaling) + ::basegfx::B2DHomMatrix aInnerPolygonTransformMatrix; + + + // apply scaling (possibly anisotrophic) to inner polygon + + + // scale inner polygon according to aspect ratio: for + // wider-than-tall bounds (nAspectRatio > 1.0), the inner + // polygon, representing the gradient focus, must have + // non-zero width. Specifically, a bound rect twice as wide as + // tall has a focus polygon of half its width. + const double nAspectRatio( rValues.mnAspectRatio ); + if( nAspectRatio > 1.0 ) + { + // width > height case + aInnerPolygonTransformMatrix.scale( 1.0 - 1.0/nAspectRatio, + 0.0 ); + } + else if( nAspectRatio < 1.0 ) + { + // width < height case + aInnerPolygonTransformMatrix.scale( 0.0, + 1.0 - nAspectRatio ); + } + else + { + // isotrophic case + aInnerPolygonTransformMatrix.scale( 0.0, 0.0 ); + } + + // and finally, add texture transform to it. + aInnerPolygonTransformMatrix *= rTextureTransform; + + // apply final matrix to polygon + aInnerPoly.transform( aInnerPolygonTransformMatrix ); + + + const sal_uInt32 nNumPoints( aOuterPoly.count() ); + ::tools::Polygon aTempPoly( static_cast<sal_uInt16>(nNumPoints+1) ); + + // increase number of steps by one: polygonal gradients have + // the outermost polygon rendered in rColor2, and the + // innermost in rColor1. The innermost polygon will never + // have zero area, thus, we must divide the interval into + // nStepCount+1 steps. For example, to create 3 steps: + + // | | + // |-------|-------|-------| + // | | + // 3 2 1 0 + + // This yields 4 tick marks, where 0 is never attained (since + // zero-area polygons typically don't display perceivable + // color). + ++nStepCount; + + rOutDev.SetLineColor(); + + basegfx::utils::KeyStopLerp aLerper(rValues.maStops); + + // fill background + rOutDev.SetFillColor( rColors.front() ); + rOutDev.DrawRect( rBounds ); + + // render polygon + // ============== + + for( unsigned int i=1,p; i<nStepCount; ++i ) + { + const double fT( i/double(nStepCount) ); + + std::ptrdiff_t nIndex; + double fAlpha; + std::tie(nIndex,fAlpha)=aLerper.lerp(fT); + + // lerp color + rOutDev.SetFillColor( + Color( static_cast<sal_uInt8>(basegfx::utils::lerp(rColors[nIndex].GetRed(),rColors[nIndex+1].GetRed(),fAlpha)), + static_cast<sal_uInt8>(basegfx::utils::lerp(rColors[nIndex].GetGreen(),rColors[nIndex+1].GetGreen(),fAlpha)), + static_cast<sal_uInt8>(basegfx::utils::lerp(rColors[nIndex].GetBlue(),rColors[nIndex+1].GetBlue(),fAlpha)) )); + + // scale and render polygon, by interpolating between + // outer and inner polygon. + + for( p=0; p<nNumPoints; ++p ) + { + const ::basegfx::B2DPoint& rOuterPoint( aOuterPoly.getB2DPoint(p) ); + const ::basegfx::B2DPoint& rInnerPoint( aInnerPoly.getB2DPoint(p) ); + + aTempPoly[static_cast<sal_uInt16>(p)] = ::Point( + basegfx::fround( fT*rInnerPoint.getX() + (1-fT)*rOuterPoint.getX() ), + basegfx::fround( fT*rInnerPoint.getY() + (1-fT)*rOuterPoint.getY() ) ); + } + + // close polygon explicitly + aTempPoly[static_cast<sal_uInt16>(p)] = aTempPoly[0]; + + // TODO(P1): compare with vcl/source/gdi/outdev4.cxx, + // OutputDevice::ImplDrawComplexGradient(), there's a note + // that on some VDev's, rendering disjunct poly-polygons + // is faster! + rOutDev.DrawPolygon( aTempPoly ); + } + } + + void doGradientFill( OutputDevice& rOutDev, + const ::canvas::ParametricPolyPolygon::Values& rValues, + const std::vector< ::Color >& rColors, + const ::basegfx::B2DHomMatrix& rTextureTransform, + const ::tools::Rectangle& rBounds, + unsigned int nStepCount ) + { + switch( rValues.meType ) + { + case ::canvas::ParametricPolyPolygon::GradientType::Linear: + fillLinearGradient( rOutDev, + rTextureTransform, + rBounds, + nStepCount, + rValues, + rColors ); + break; + + case ::canvas::ParametricPolyPolygon::GradientType::Elliptical: + case ::canvas::ParametricPolyPolygon::GradientType::Rectangular: + fillPolygonalGradient( rOutDev, + rTextureTransform, + rBounds, + nStepCount, + rValues, + rColors ); + break; + + default: + ENSURE_OR_THROW( false, + "CanvasHelper::doGradientFill(): Unexpected case" ); + } + } + + int numColorSteps( const ::Color& rColor1, const ::Color& rColor2 ) + { + return std::max( + std::abs( rColor1.GetRed() - rColor2.GetRed() ), + std::max( + std::abs( rColor1.GetGreen() - rColor2.GetGreen() ), + std::abs( rColor1.GetBlue() - rColor2.GetBlue() ) ) ); + } + + bool gradientFill( OutputDevice& rOutDev, + OutputDevice* p2ndOutDev, + const ::canvas::ParametricPolyPolygon::Values& rValues, + const std::vector< ::Color >& rColors, + const ::tools::PolyPolygon& rPoly, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState, + const rendering::Texture& texture, + int nTransparency ) + { + // TODO(T2): It is maybe necessary to lock here, should + // maGradientPoly someday cease to be const. But then, beware of + // deadlocks, canvashelper calls this method with locked own + // mutex. + + // calc step size + + int nColorSteps = 0; + for( size_t i=0; i<rColors.size()-1; ++i ) + nColorSteps += numColorSteps(rColors[i],rColors[i+1]); + + ::basegfx::B2DHomMatrix aTotalTransform; + const int nStepCount= + ::canvas::tools::calcGradientStepCount(aTotalTransform, + viewState, + renderState, + texture, + nColorSteps); + + rOutDev.SetLineColor(); + + // determine maximal bound rect of texture-filled + // polygon + const ::tools::Rectangle aPolygonDeviceRectOrig( + rPoly.GetBoundRect() ); + + if( tools::isRectangle( rPoly ) ) + { + // use optimized output path + + + // this distinction really looks like a + // micro-optimization, but in fact greatly speeds up + // especially complex gradients. That's because when using + // clipping, we can output polygons instead of + // poly-polygons, and don't have to output the gradient + // twice for XOR + + rOutDev.Push( vcl::PushFlags::CLIPREGION ); + rOutDev.IntersectClipRegion( aPolygonDeviceRectOrig ); + doGradientFill( rOutDev, + rValues, + rColors, + aTotalTransform, + aPolygonDeviceRectOrig, + nStepCount ); + rOutDev.Pop(); + + if( p2ndOutDev && nTransparency < 253 ) + { + // HACK. Normally, CanvasHelper does not care about + // actually what mp2ndOutDev is... well, here we do & + // assume a 1bpp target - everything beyond 97% + // transparency is fully transparent + p2ndOutDev->SetFillColor( COL_BLACK ); + p2ndOutDev->DrawRect( aPolygonDeviceRectOrig ); + } + } + else + { + const vcl::Region aPolyClipRegion( rPoly ); + + rOutDev.Push( vcl::PushFlags::CLIPREGION ); + rOutDev.IntersectClipRegion( aPolyClipRegion ); + + doGradientFill( rOutDev, + rValues, + rColors, + aTotalTransform, + aPolygonDeviceRectOrig, + nStepCount ); + rOutDev.Pop(); + + if( p2ndOutDev && nTransparency < 253 ) + { + // HACK. Normally, CanvasHelper does not care about + // actually what mp2ndOutDev is... well, here we do & + // assume a 1bpp target - everything beyond 97% + // transparency is fully transparent + p2ndOutDev->SetFillColor( COL_BLACK ); + p2ndOutDev->DrawPolyPolygon( rPoly ); + } + } + +#ifdef DEBUG_CANVAS_CANVASHELPER_TEXTUREFILL + // extra-verbosity + { + ::basegfx::B2DRectangle aRect(0.0, 0.0, 1.0, 1.0); + ::basegfx::B2DRectangle aTextureDeviceRect; + ::basegfx::B2DHomMatrix aTextureTransform; + ::canvas::tools::calcTransformedRectBounds( aTextureDeviceRect, + aRect, + aTextureTransform ); + rOutDev.SetLineColor( COL_RED ); + rOutDev.SetFillColor(); + rOutDev.DrawRect( vcl::unotools::rectangleFromB2DRectangle( aTextureDeviceRect ) ); + + rOutDev.SetLineColor( COL_BLUE ); + ::tools::Polygon aPoly1( + vcl::unotools::rectangleFromB2DRectangle( aRect )); + ::basegfx::B2DPolygon aPoly2( aPoly1.getB2DPolygon() ); + aPoly2.transform( aTextureTransform ); + ::tools::Polygon aPoly3( aPoly2 ); + rOutDev.DrawPolygon( aPoly3 ); + } +#endif + + return true; + } + } + + uno::Reference< rendering::XCachedPrimitive > CanvasHelper::fillTexturedPolyPolygon( const rendering::XCanvas* pCanvas, + const uno::Reference< rendering::XPolyPolygon2D >& xPolyPolygon, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState, + const uno::Sequence< rendering::Texture >& textures ) + { + ENSURE_ARG_OR_THROW( xPolyPolygon.is(), + "CanvasHelper::fillPolyPolygon(): polygon is NULL"); + ENSURE_ARG_OR_THROW( textures.hasElements(), + "CanvasHelper::fillTexturedPolyPolygon: empty texture sequence"); + + if( mpOutDevProvider ) + { + tools::OutDevStateKeeper aStateKeeper( mpProtectedOutDevProvider ); + + const int nTransparency( setupOutDevState( viewState, renderState, IGNORE_COLOR ) ); + ::tools::PolyPolygon aPolyPoly( tools::mapPolyPolygon( + ::basegfx::unotools::b2DPolyPolygonFromXPolyPolygon2D(xPolyPolygon), + viewState, renderState ) ); + + // TODO(F1): Multi-texturing + if( textures[0].Gradient.is() ) + { + // try to cast XParametricPolyPolygon2D reference to + // our implementation class. + ::canvas::ParametricPolyPolygon* pGradient = + dynamic_cast< ::canvas::ParametricPolyPolygon* >( textures[0].Gradient.get() ); + + if( pGradient && pGradient->getValues().maColors.hasElements() ) + { + // copy state from Gradient polypoly locally + // (given object might change!) + const ::canvas::ParametricPolyPolygon::Values& rValues( + pGradient->getValues() ); + + if( rValues.maColors.getLength() < 2 ) + { + rendering::RenderState aTempState=renderState; + aTempState.DeviceColor = rValues.maColors[0]; + fillPolyPolygon(pCanvas, xPolyPolygon, viewState, aTempState); + } + else + { + std::vector< ::Color > aColors(rValues.maColors.getLength()); + std::transform(&rValues.maColors[0], + &rValues.maColors[0]+rValues.maColors.getLength(), + aColors.begin(), + [](const uno::Sequence< double >& aColor) { + return vcl::unotools::stdColorSpaceSequenceToColor( aColor ); + } ); + + // TODO(E1): Return value + // TODO(F1): FillRule + gradientFill( mpOutDevProvider->getOutDev(), + mp2ndOutDevProvider ? &mp2ndOutDevProvider->getOutDev() : nullptr, + rValues, + aColors, + aPolyPoly, + viewState, + renderState, + textures[0], + nTransparency ); + } + } + else + { + // TODO(F1): The generic case is missing here + ENSURE_OR_THROW( false, + "CanvasHelper::fillTexturedPolyPolygon(): unknown parametric polygon encountered" ); + } + } + else if( textures[0].Bitmap.is() ) + { + geometry::IntegerSize2D aBmpSize( textures[0].Bitmap->getSize() ); + + ENSURE_ARG_OR_THROW( aBmpSize.Width != 0 && + aBmpSize.Height != 0, + "CanvasHelper::fillTexturedPolyPolygon(): zero-sized texture bitmap" ); + + // determine maximal bound rect of texture-filled + // polygon + const ::tools::Rectangle aPolygonDeviceRect( + aPolyPoly.GetBoundRect() ); + + + // first of all, determine whether we have a + // drawBitmap() in disguise + // ========================================= + + const bool bRectangularPolygon( tools::isRectangle( aPolyPoly ) ); + + ::basegfx::B2DHomMatrix aTotalTransform; + ::canvas::tools::mergeViewAndRenderTransform(aTotalTransform, + viewState, + renderState); + ::basegfx::B2DHomMatrix aTextureTransform; + ::basegfx::unotools::homMatrixFromAffineMatrix( aTextureTransform, + textures[0].AffineTransform ); + + aTotalTransform *= aTextureTransform; + + const ::basegfx::B2DRectangle aRect(0.0, 0.0, 1.0, 1.0); + ::basegfx::B2DRectangle aTextureDeviceRect; + ::canvas::tools::calcTransformedRectBounds( aTextureDeviceRect, + aRect, + aTotalTransform ); + + const ::tools::Rectangle aIntegerTextureDeviceRect( + vcl::unotools::rectangleFromB2DRectangle( aTextureDeviceRect ) ); + + if( bRectangularPolygon && + aIntegerTextureDeviceRect == aPolygonDeviceRect ) + { + rendering::RenderState aLocalState( renderState ); + ::canvas::tools::appendToRenderState(aLocalState, + aTextureTransform); + ::basegfx::B2DHomMatrix aScaleCorrection; + aScaleCorrection.scale( 1.0/aBmpSize.Width, + 1.0/aBmpSize.Height ); + ::canvas::tools::appendToRenderState(aLocalState, + aScaleCorrection); + + // need alpha modulation? + if( !::rtl::math::approxEqual( textures[0].Alpha, + 1.0 ) ) + { + // setup alpha modulation values + aLocalState.DeviceColor.realloc(4); + double* pColor = aLocalState.DeviceColor.getArray(); + pColor[0] = + pColor[1] = + pColor[2] = 0.0; + pColor[3] = textures[0].Alpha; + + return drawBitmapModulated( pCanvas, + textures[0].Bitmap, + viewState, + aLocalState ); + } + else + { + return drawBitmap( pCanvas, + textures[0].Bitmap, + viewState, + aLocalState ); + } + } + else + { + // No easy mapping to drawBitmap() - calculate + // texturing parameters + // =========================================== + + BitmapEx aBmpEx( tools::bitmapExFromXBitmap( textures[0].Bitmap ) ); + + // scale down bitmap to [0,1]x[0,1] rect, as required + // from the XCanvas interface. + ::basegfx::B2DHomMatrix aScaling; + ::basegfx::B2DHomMatrix aPureTotalTransform; // pure view*render*texture transform + aScaling.scale( 1.0/aBmpSize.Width, + 1.0/aBmpSize.Height ); + + aTotalTransform = aTextureTransform * aScaling; + aPureTotalTransform = aTextureTransform; + + // combine with view and render transform + ::basegfx::B2DHomMatrix aMatrix; + ::canvas::tools::mergeViewAndRenderTransform(aMatrix, viewState, renderState); + + // combine all three transformations into one + // global texture-to-device-space transformation + aTotalTransform *= aMatrix; + aPureTotalTransform *= aMatrix; + + // analyze transformation, and setup an + // appropriate GraphicObject + ::basegfx::B2DVector aScale; + ::basegfx::B2DPoint aOutputPos; + double nRotate; + double nShearX; + aTotalTransform.decompose( aScale, aOutputPos, nRotate, nShearX ); + + GraphicAttr aGrfAttr; + GraphicObjectSharedPtr pGrfObj; + + if( ::basegfx::fTools::equalZero( nShearX ) ) + { + // no shear, GraphicObject is enough (the + // GraphicObject only supports scaling, rotation + // and translation) + + // #i75339# don't apply mirror flags, having + // negative size values is enough to make + // GraphicObject flip the bitmap + + // The angle has to be mapped from radian to tenths of + // degrees with the orientation reversed: [0,2Pi) -> + // (3600,0]. Note that the original angle may have + // values outside the [0,2Pi) interval. + const double nAngleInTenthOfDegrees (3600.0 - basegfx::rad2deg<10>(nRotate)); + aGrfAttr.SetRotation( Degree10(::basegfx::fround(nAngleInTenthOfDegrees)) ); + + pGrfObj = std::make_shared<GraphicObject>( aBmpEx ); + } + else + { + // modify output position, to account for the fact + // that transformBitmap() always normalizes its output + // bitmap into the smallest enclosing box. + ::basegfx::B2DRectangle aDestRect; + ::canvas::tools::calcTransformedRectBounds( aDestRect, + ::basegfx::B2DRectangle(0, + 0, + aBmpSize.Width, + aBmpSize.Height), + aMatrix ); + + aOutputPos.setX( aDestRect.getMinX() ); + aOutputPos.setY( aDestRect.getMinY() ); + + // complex transformation, use generic affine bitmap + // transformation + aBmpEx = tools::transformBitmap( aBmpEx, + aTotalTransform); + + pGrfObj = std::make_shared<GraphicObject>( aBmpEx ); + + // clear scale values, generated bitmap already + // contains scaling + aScale.setX( 1.0 ); aScale.setY( 1.0 ); + + // update bitmap size, bitmap has changed above. + aBmpSize = vcl::unotools::integerSize2DFromSize(aBmpEx.GetSizePixel()); + } + + + // render texture tiled into polygon + // ================================= + + // calc device space direction vectors. We employ + // the following approach for tiled output: the + // texture bitmap is output in texture space + // x-major order, i.e. tile neighbors in texture + // space x direction are rendered back-to-back in + // device coordinate space (after the full device + // transformation). Thus, the aNextTile* vectors + // denote the output position updates in device + // space, to get from one tile to the next. + ::basegfx::B2DVector aNextTileX( 1.0, 0.0 ); + ::basegfx::B2DVector aNextTileY( 0.0, 1.0 ); + aNextTileX *= aPureTotalTransform; + aNextTileY *= aPureTotalTransform; + + ::basegfx::B2DHomMatrix aInverseTextureTransform( aPureTotalTransform ); + + ENSURE_ARG_OR_THROW( aInverseTextureTransform.isInvertible(), + "CanvasHelper::fillTexturedPolyPolygon(): singular texture matrix" ); + + aInverseTextureTransform.invert(); + + // calc bound rect of extended texture area in + // device coordinates. Therefore, we first calc + // the area of the polygon bound rect in texture + // space. To maintain texture phase, this bound + // rect is then extended to integer coordinates + // (extended, because shrinking might leave some + // inner polygon areas unfilled). + // Finally, the bound rect is transformed back to + // device coordinate space, were we determine the + // start point from it. + ::basegfx::B2DRectangle aTextureSpacePolygonRect; + ::canvas::tools::calcTransformedRectBounds( aTextureSpacePolygonRect, + vcl::unotools::b2DRectangleFromRectangle(aPolygonDeviceRect), + aInverseTextureTransform ); + + // calc left, top of extended polygon rect in + // texture space, create one-texture instance rect + // from it (i.e. rect from start point extending + // 1.0 units to the right and 1.0 units to the + // bottom). Note that the rounding employed here + // is a bit subtle, since we need to round up/down + // as _soon_ as any fractional amount is + // encountered. This is to ensure that the full + // polygon area is filled with texture tiles. + const sal_Int32 nX1( ::canvas::tools::roundDown( aTextureSpacePolygonRect.getMinX() ) ); + const sal_Int32 nY1( ::canvas::tools::roundDown( aTextureSpacePolygonRect.getMinY() ) ); + const sal_Int32 nX2( ::canvas::tools::roundUp( aTextureSpacePolygonRect.getMaxX() ) ); + const sal_Int32 nY2( ::canvas::tools::roundUp( aTextureSpacePolygonRect.getMaxY() ) ); + const ::basegfx::B2DRectangle aSingleTextureRect( + nX1, nY1, + nX1 + 1.0, + nY1 + 1.0 ); + + // and convert back to device space + ::basegfx::B2DRectangle aSingleDeviceTextureRect; + ::canvas::tools::calcTransformedRectBounds( aSingleDeviceTextureRect, + aSingleTextureRect, + aPureTotalTransform ); + + const ::Point aPtRepeat( vcl::unotools::pointFromB2DPoint( + aSingleDeviceTextureRect.getMinimum() ) ); + const ::Size aSz( ::basegfx::fround( aScale.getX() * aBmpSize.Width ), + ::basegfx::fround( aScale.getY() * aBmpSize.Height ) ); + const ::Size aIntegerNextTileX( vcl::unotools::sizeFromB2DSize(aNextTileX) ); + const ::Size aIntegerNextTileY( vcl::unotools::sizeFromB2DSize(aNextTileY) ); + + const ::Point aPt( textures[0].RepeatModeX == rendering::TexturingMode::NONE ? + ::basegfx::fround( aOutputPos.getX() ) : aPtRepeat.X(), + textures[0].RepeatModeY == rendering::TexturingMode::NONE ? + ::basegfx::fround( aOutputPos.getY() ) : aPtRepeat.Y() ); + const sal_Int32 nTilesX( textures[0].RepeatModeX == rendering::TexturingMode::NONE ? + 1 : nX2 - nX1 ); + const sal_Int32 nTilesY( textures[0].RepeatModeX == rendering::TexturingMode::NONE ? + 1 : nY2 - nY1 ); + + OutputDevice& rOutDev( mpOutDevProvider->getOutDev() ); + + if( bRectangularPolygon ) + { + // use optimized output path + + + // this distinction really looks like a + // micro-optimization, but in fact greatly speeds up + // especially complex fills. That's because when using + // clipping, we can output polygons instead of + // poly-polygons, and don't have to output the gradient + // twice for XOR + + // setup alpha modulation + if( !::rtl::math::approxEqual( textures[0].Alpha, + 1.0 ) ) + { + // TODO(F1): Note that the GraphicManager has + // a subtle difference in how it calculates + // the resulting alpha value: it's using the + // inverse alpha values (i.e. 'transparency'), + // and calculates transOrig + transModulate, + // instead of transOrig + transModulate - + // transOrig*transModulate (which would be + // equivalent to the origAlpha*modulateAlpha + // the DX canvas performs) + aGrfAttr.SetAlpha( + static_cast< sal_uInt8 >( + ::basegfx::fround( 255.0 * textures[0].Alpha ) ) ); + } + + rOutDev.IntersectClipRegion( aPolygonDeviceRect ); + textureFill( rOutDev, + *pGrfObj, + aPt, + aIntegerNextTileX, + aIntegerNextTileY, + nTilesX, + nTilesY, + aSz, + aGrfAttr ); + + if( mp2ndOutDevProvider ) + { + OutputDevice& r2ndOutDev( mp2ndOutDevProvider->getOutDev() ); + r2ndOutDev.IntersectClipRegion( aPolygonDeviceRect ); + textureFill( r2ndOutDev, + *pGrfObj, + aPt, + aIntegerNextTileX, + aIntegerNextTileY, + nTilesX, + nTilesY, + aSz, + aGrfAttr ); + } + } + else + { + // output texture the hard way: XORing out the + // polygon + // =========================================== + + if( !::rtl::math::approxEqual( textures[0].Alpha, + 1.0 ) ) + { + // uh-oh. alpha blending is required, + // cannot do direct XOR, but have to + // prepare the filled polygon within a + // VDev + ScopedVclPtrInstance< VirtualDevice > pVDev( rOutDev ); + pVDev->SetOutputSizePixel( aPolygonDeviceRect.GetSize() ); + + // shift output to origin of VDev + const ::Point aOutPos( aPt - aPolygonDeviceRect.TopLeft() ); + aPolyPoly.Translate( ::Point( -aPolygonDeviceRect.Left(), + -aPolygonDeviceRect.Top() ) ); + + const vcl::Region aPolyClipRegion( aPolyPoly ); + + pVDev->SetClipRegion( aPolyClipRegion ); + textureFill( *pVDev, + *pGrfObj, + aOutPos, + aIntegerNextTileX, + aIntegerNextTileY, + nTilesX, + nTilesY, + aSz, + aGrfAttr ); + + // output VDev content alpha-blended to + // target position. + const ::Point aEmptyPoint; + BitmapEx aContentBmp( + pVDev->GetBitmapEx( aEmptyPoint, + pVDev->GetOutputSizePixel() ) ); + + sal_uInt8 nCol( static_cast< sal_uInt8 >( + ::basegfx::fround( 255.0*( 1.0 - textures[0].Alpha ) ) ) ); + AlphaMask aAlpha( pVDev->GetOutputSizePixel(), + &nCol ); + + BitmapEx aOutputBmpEx( aContentBmp.GetBitmap(), aAlpha ); + rOutDev.DrawBitmapEx( aPolygonDeviceRect.TopLeft(), + aOutputBmpEx ); + + if( mp2ndOutDevProvider ) + mp2ndOutDevProvider->getOutDev().DrawBitmapEx( aPolygonDeviceRect.TopLeft(), + aOutputBmpEx ); + } + else + { + const vcl::Region aPolyClipRegion( aPolyPoly ); + + rOutDev.Push( vcl::PushFlags::CLIPREGION ); + rOutDev.IntersectClipRegion( aPolyClipRegion ); + + textureFill( rOutDev, + *pGrfObj, + aPt, + aIntegerNextTileX, + aIntegerNextTileY, + nTilesX, + nTilesY, + aSz, + aGrfAttr ); + rOutDev.Pop(); + + if( mp2ndOutDevProvider ) + { + OutputDevice& r2ndOutDev( mp2ndOutDevProvider->getOutDev() ); + r2ndOutDev.Push( vcl::PushFlags::CLIPREGION ); + + r2ndOutDev.IntersectClipRegion( aPolyClipRegion ); + textureFill( r2ndOutDev, + *pGrfObj, + aPt, + aIntegerNextTileX, + aIntegerNextTileY, + nTilesX, + nTilesY, + aSz, + aGrfAttr ); + r2ndOutDev.Pop(); + } + } + } + } + } + } + + // TODO(P1): Provide caching here. + return uno::Reference< rendering::XCachedPrimitive >(nullptr); + } + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/vcl/devicehelper.cxx b/canvas/source/vcl/devicehelper.cxx new file mode 100644 index 0000000000..40d2575f88 --- /dev/null +++ b/canvas/source/vcl/devicehelper.cxx @@ -0,0 +1,217 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <basegfx/utils/canvastools.hxx> +#include <basegfx/utils/unopolypolygon.hxx> +#include <canvas/canvastools.hxx> +#include <tools/stream.hxx> +#include <vcl/canvastools.hxx> +#include <vcl/dibtools.hxx> + +#include "canvasbitmap.hxx" +#include "devicehelper.hxx" + +using namespace ::com::sun::star; + +namespace vclcanvas +{ + DeviceHelper::DeviceHelper() + {} + + void DeviceHelper::init( const OutDevProviderSharedPtr& rOutDev ) + { + mpOutDev = rOutDev; + } + + geometry::RealSize2D DeviceHelper::getPhysicalResolution() + { + if( !mpOutDev ) + return ::canvas::tools::createInfiniteSize2D(); // we're disposed + + // Map a one-by-one millimeter box to pixel + OutputDevice& rOutDev = mpOutDev->getOutDev(); + const MapMode aOldMapMode( rOutDev.GetMapMode() ); + rOutDev.SetMapMode( MapMode(MapUnit::MapMM) ); + const Size aPixelSize( rOutDev.LogicToPixel(Size(1,1)) ); + rOutDev.SetMapMode( aOldMapMode ); + + return vcl::unotools::size2DFromSize( aPixelSize ); + } + + geometry::RealSize2D DeviceHelper::getPhysicalSize() + { + if( !mpOutDev ) + return ::canvas::tools::createInfiniteSize2D(); // we're disposed + + // Map the pixel dimensions of the output window to millimeter + OutputDevice& rOutDev = mpOutDev->getOutDev(); + const MapMode aOldMapMode( rOutDev.GetMapMode() ); + rOutDev.SetMapMode( MapMode(MapUnit::MapMM) ); + const Size aLogSize( rOutDev.PixelToLogic(rOutDev.GetOutputSizePixel()) ); + rOutDev.SetMapMode( aOldMapMode ); + + return vcl::unotools::size2DFromSize( aLogSize ); + } + + uno::Reference< rendering::XLinePolyPolygon2D > DeviceHelper::createCompatibleLinePolyPolygon( + const uno::Reference< rendering::XGraphicDevice >& , + const uno::Sequence< uno::Sequence< geometry::RealPoint2D > >& points ) + { + uno::Reference< rendering::XLinePolyPolygon2D > xPoly; + if( !mpOutDev ) + return xPoly; // we're disposed + + xPoly.set( new ::basegfx::unotools::UnoPolyPolygon( + ::basegfx::unotools::polyPolygonFromPoint2DSequenceSequence( points ) ) ); + // vcl only handles even_odd polygons + xPoly->setFillRule(rendering::FillRule_EVEN_ODD); + + return xPoly; + } + + uno::Reference< rendering::XBezierPolyPolygon2D > DeviceHelper::createCompatibleBezierPolyPolygon( + const uno::Reference< rendering::XGraphicDevice >& , + const uno::Sequence< uno::Sequence< geometry::RealBezierSegment2D > >& points ) + { + uno::Reference< rendering::XBezierPolyPolygon2D > xPoly; + if( !mpOutDev ) + return xPoly; // we're disposed + + xPoly.set( new ::basegfx::unotools::UnoPolyPolygon( + ::basegfx::unotools::polyPolygonFromBezier2DSequenceSequence( points ) ) ); + // vcl only handles even_odd polygons + xPoly->setFillRule(rendering::FillRule_EVEN_ODD); + + return xPoly; + } + + uno::Reference< rendering::XBitmap > DeviceHelper::createCompatibleBitmap( + const uno::Reference< rendering::XGraphicDevice >& rDevice, + const geometry::IntegerSize2D& size ) + { + if( !mpOutDev ) + return uno::Reference< rendering::XBitmap >(); // we're disposed + + return uno::Reference< rendering::XBitmap >( + new CanvasBitmap( vcl::unotools::sizeFromIntegerSize2D(size), + false, + *rDevice, + mpOutDev ) ); + } + + uno::Reference< rendering::XVolatileBitmap > DeviceHelper::createVolatileBitmap( + const uno::Reference< rendering::XGraphicDevice >& , + const geometry::IntegerSize2D& ) + { + return uno::Reference< rendering::XVolatileBitmap >(); + } + + uno::Reference< rendering::XBitmap > DeviceHelper::createCompatibleAlphaBitmap( + const uno::Reference< rendering::XGraphicDevice >& rDevice, + const geometry::IntegerSize2D& size ) + { + if( !mpOutDev ) + return uno::Reference< rendering::XBitmap >(); // we're disposed + + return uno::Reference< rendering::XBitmap >( + new CanvasBitmap( vcl::unotools::sizeFromIntegerSize2D(size), + true, + *rDevice, + mpOutDev ) ); + } + + uno::Reference< rendering::XVolatileBitmap > DeviceHelper::createVolatileAlphaBitmap( + const uno::Reference< rendering::XGraphicDevice >& , + const geometry::IntegerSize2D& ) + { + return uno::Reference< rendering::XVolatileBitmap >(); + } + + void DeviceHelper::disposing() + { + // release all references + mpOutDev.reset(); + } + + uno::Any DeviceHelper::isAccelerated() const + { + return css::uno::Any(false); + } + + uno::Any DeviceHelper::getDeviceHandle() const + { + if( !mpOutDev ) + return uno::Any(); + + return uno::Any( + reinterpret_cast< sal_Int64 >(&mpOutDev->getOutDev()) ); + } + + uno::Any DeviceHelper::getSurfaceHandle() const + { + return getDeviceHandle(); + } + + namespace + { + uno::Reference<rendering::XColorSpace>& GetDeviceColorSpace() + { + static uno::Reference<rendering::XColorSpace> xColorSpace = + []() + { + auto xTmp = canvas::tools::getStdColorSpace(); + assert( xTmp.is() ); + return xTmp; + }(); + return xColorSpace; + }; + } + + uno::Reference<rendering::XColorSpace> const & DeviceHelper::getColorSpace() const + { + // always the same + return GetDeviceColorSpace(); + } + + void DeviceHelper::dumpScreenContent() const + { + static sal_Int32 nFilePostfixCount(0); + + if( !mpOutDev ) + return; + + OUString aFilename = "dbg_frontbuffer" + OUString::number(nFilePostfixCount) + ".bmp"; + + SvFileStream aStream( aFilename, StreamMode::STD_READWRITE ); + + const ::Point aEmptyPoint; + OutputDevice& rOutDev = mpOutDev->getOutDev(); + bool bOldMap( rOutDev.IsMapModeEnabled() ); + rOutDev.EnableMapMode( false ); + WriteDIB(rOutDev.GetBitmapEx(aEmptyPoint, rOutDev.GetOutputSizePixel()), aStream, false); + rOutDev.EnableMapMode( bOldMap ); + + ++nFilePostfixCount; + } + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/vcl/devicehelper.hxx b/canvas/source/vcl/devicehelper.hxx new file mode 100644 index 0000000000..f8ff12aedc --- /dev/null +++ b/canvas/source/vcl/devicehelper.hxx @@ -0,0 +1,86 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/rendering/XGraphicDevice.hpp> + +#include "outdevprovider.hxx" + + +/* Definition of DeviceHelper class */ + +namespace vclcanvas +{ + class DeviceHelper + { + public: + DeviceHelper(); + + /// make noncopyable + DeviceHelper(const DeviceHelper&) = delete; + const DeviceHelper& operator=(const DeviceHelper&) = delete; + + void init( const OutDevProviderSharedPtr& rOutDev ); + + /// Dispose all internal references + void disposing(); + + // XWindowGraphicDevice + css::geometry::RealSize2D getPhysicalResolution(); + css::geometry::RealSize2D getPhysicalSize(); + css::uno::Reference< css::rendering::XLinePolyPolygon2D > createCompatibleLinePolyPolygon( + const css::uno::Reference< css::rendering::XGraphicDevice >& rDevice, + const css::uno::Sequence< css::uno::Sequence< css::geometry::RealPoint2D > >& points ); + css::uno::Reference< css::rendering::XBezierPolyPolygon2D > createCompatibleBezierPolyPolygon( + const css::uno::Reference< css::rendering::XGraphicDevice >& rDevice, + const css::uno::Sequence< css::uno::Sequence< css::geometry::RealBezierSegment2D > >& points ); + css::uno::Reference< css::rendering::XBitmap > createCompatibleBitmap( + const css::uno::Reference< css::rendering::XGraphicDevice >& rDevice, + const css::geometry::IntegerSize2D& size ); + css::uno::Reference< css::rendering::XVolatileBitmap > createVolatileBitmap( + const css::uno::Reference< css::rendering::XGraphicDevice >& rDevice, + const css::geometry::IntegerSize2D& size ); + css::uno::Reference< css::rendering::XBitmap > createCompatibleAlphaBitmap( + const css::uno::Reference< css::rendering::XGraphicDevice >& rDevice, + const css::geometry::IntegerSize2D& size ); + css::uno::Reference< css::rendering::XVolatileBitmap > createVolatileAlphaBitmap( + const css::uno::Reference< css::rendering::XGraphicDevice >& rDevice, + const css::geometry::IntegerSize2D& size ); + + css::uno::Any isAccelerated() const; + css::uno::Any getDeviceHandle() const; + css::uno::Any getSurfaceHandle() const; + css::uno::Reference< + css::rendering::XColorSpace > const & getColorSpace() const; + + const OutDevProviderSharedPtr& getOutDev() const { return mpOutDev; } + + /** called when DumpScreenContent property is enabled on + XGraphicDevice, and writes out bitmaps of current screen. + */ + void dumpScreenContent() const; + + private: + /// For retrieving device info + OutDevProviderSharedPtr mpOutDev; + }; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/vcl/impltools.cxx b/canvas/source/vcl/impltools.cxx new file mode 100644 index 0000000000..0a44f22096 --- /dev/null +++ b/canvas/source/vcl/impltools.cxx @@ -0,0 +1,266 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/numeric/ftools.hxx> +#include <basegfx/point/b2dpoint.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/range/b2drectangle.hxx> +#include <basegfx/utils/canvastools.hxx> +#include <basegfx/tuple/b2dtuple.hxx> +#include <rtl/math.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <sal/log.hxx> +#include <vcl/bitmapex.hxx> +#include <vcl/canvastools.hxx> +#include <vcl/BitmapTools.hxx> +#include <vcl/metric.hxx> +#include <vcl/skia/SkiaHelper.hxx> + +#include <canvas/canvastools.hxx> + +#include "canvasbitmap.hxx" +#include "impltools.hxx" +#include "spritecanvas.hxx" + + +using namespace ::com::sun::star; + +namespace vclcanvas::tools +{ + ::BitmapEx bitmapExFromXBitmap( const uno::Reference< rendering::XBitmap >& xBitmap ) + { + // TODO(F3): CanvasCustomSprite should also be tunnelled + // through (also implements XIntegerBitmap interface) + CanvasBitmap* pBitmapImpl = dynamic_cast< CanvasBitmap* >( xBitmap.get() ); + + if( pBitmapImpl ) + { + return pBitmapImpl->getBitmap(); + } + else + { + SpriteCanvas* pCanvasImpl = dynamic_cast< SpriteCanvas* >( xBitmap.get() ); + if( pCanvasImpl && pCanvasImpl->getBackBuffer() ) + { + // TODO(F3): mind the plain Canvas impl. Consolidate with CWS canvas05 + const ::OutputDevice& rDev( pCanvasImpl->getBackBuffer()->getOutDev() ); + const ::Point aEmptyPoint; + return rDev.GetBitmapEx( aEmptyPoint, + rDev.GetOutputSizePixel() ); + } + + // TODO(F2): add support for floating point bitmap formats + uno::Reference< rendering::XIntegerReadOnlyBitmap > xIntBmp( + xBitmap, uno::UNO_QUERY_THROW ); + + ::BitmapEx aBmpEx = vcl::unotools::bitmapExFromXBitmap( xIntBmp ); + if( !aBmpEx.IsEmpty() ) + return aBmpEx; + + // TODO(F1): extract pixel from XBitmap interface + ENSURE_OR_THROW( false, + "bitmapExFromXBitmap(): could not extract bitmap" ); + } + + return ::BitmapEx(); + } + + bool setupFontTransform( ::Point& o_rPoint, + vcl::Font& io_rVCLFont, + const rendering::ViewState& rViewState, + const rendering::RenderState& rRenderState, + ::OutputDevice const & rOutDev ) + { + ::basegfx::B2DHomMatrix aMatrix; + + ::canvas::tools::mergeViewAndRenderTransform(aMatrix, + rViewState, + rRenderState); + + ::basegfx::B2DTuple aScale; + ::basegfx::B2DTuple aTranslate; + double nRotate, nShearX; + + aMatrix.decompose( aScale, aTranslate, nRotate, nShearX ); + + // query font metric _before_ tampering with width and height + if( !::rtl::math::approxEqual(aScale.getX(), aScale.getY()) ) + { + // retrieve true font width + const sal_Int32 nFontWidth( rOutDev.GetFontMetric( io_rVCLFont ).GetAverageFontWidth() ); + + const sal_Int32 nScaledFontWidth( ::basegfx::fround(nFontWidth * aScale.getX()) ); + + if( !nScaledFontWidth ) + { + // scale is smaller than one pixel - disable text + // output altogether + return false; + } + + io_rVCLFont.SetAverageFontWidth( nScaledFontWidth ); + } + + if( !::rtl::math::approxEqual(aScale.getY(), 1.0) ) + { + const sal_Int32 nFontHeight( io_rVCLFont.GetFontHeight() ); + io_rVCLFont.SetFontHeight( ::basegfx::fround(nFontHeight * aScale.getY()) ); + } + + io_rVCLFont.SetOrientation( Degree10( ::basegfx::fround(-basegfx::rad2deg<10>(fmod(nRotate, 2*M_PI))) ) ); + + // TODO(F2): Missing functionality in VCL: shearing + o_rPoint.setX( ::basegfx::fround(aTranslate.getX()) ); + o_rPoint.setY( ::basegfx::fround(aTranslate.getY()) ); + + return true; + } + + void setupFontWidth(const css::geometry::Matrix2D& rFontMatrix, + vcl::Font& rFont, + OutputDevice& rOutDev) + { + rFont.SetFontSize(Size(0, rFont.GetFontHeight())); + + if (!::rtl::math::approxEqual(rFontMatrix.m00, rFontMatrix.m11)) + { + const bool bOldMapState(rOutDev.IsMapModeEnabled()); + rOutDev.EnableMapMode(false); + + const Size aSize = rOutDev.GetFontMetric(rFont).GetFontSize(); + + const double fDividend(rFontMatrix.m10 + rFontMatrix.m11); + double fStretch = rFontMatrix.m00 + rFontMatrix.m01; + + if (!::basegfx::fTools::equalZero(fDividend)) + fStretch /= fDividend; + + const ::tools::Long nNewWidth = ::basegfx::fround(aSize.Width() * fStretch); + + rFont.SetAverageFontWidth(nNewWidth); + + rOutDev.EnableMapMode(bOldMapState); + } + } + + bool isRectangle( const ::tools::PolyPolygon& rPolyPoly ) + { + // exclude some cheap cases first + if( rPolyPoly.Count() != 1 ) + return false; + + const ::tools::Polygon& rPoly( rPolyPoly[0] ); + + sal_uInt16 nCount( rPoly.GetSize() ); + if( nCount < 4 ) + return false; + + // delegate to basegfx + return ::basegfx::utils::isRectangle( rPoly.getB2DPolygon() ); + } + + + // VCL-Canvas related + + + ::Point mapRealPoint2D( const geometry::RealPoint2D& rPoint, + const rendering::ViewState& rViewState, + const rendering::RenderState& rRenderState ) + { + ::basegfx::B2DPoint aPoint( ::basegfx::unotools::b2DPointFromRealPoint2D(rPoint) ); + + ::basegfx::B2DHomMatrix aMatrix; + aPoint *= ::canvas::tools::mergeViewAndRenderTransform(aMatrix, + rViewState, + rRenderState); + + return vcl::unotools::pointFromB2DPoint( aPoint ); + } + + ::tools::PolyPolygon mapPolyPolygon( const ::basegfx::B2DPolyPolygon& rPoly, + const rendering::ViewState& rViewState, + const rendering::RenderState& rRenderState ) + { + ::basegfx::B2DHomMatrix aMatrix; + ::canvas::tools::mergeViewAndRenderTransform(aMatrix, + rViewState, + rRenderState); + + ::basegfx::B2DPolyPolygon aTemp( rPoly ); + + aTemp.transform( aMatrix ); + + return ::tools::PolyPolygon( aTemp ); + } + + ::BitmapEx transformBitmap( const BitmapEx& rBitmap, + const ::basegfx::B2DHomMatrix& rTransform ) + { + SAL_INFO( "canvas.vcl", "::vclcanvas::tools::transformBitmap()" ); + SAL_INFO( "canvas.vcl", "::vclcanvas::tools::transformBitmap: 0x" << std::hex << &rBitmap ); + + // calc transformation and size of bitmap to be + // generated. Note, that the translational components are + // deleted from the transformation; this can be handled by + // an offset when painting the bitmap + const Size aBmpSize( rBitmap.GetSizePixel() ); + ::basegfx::B2DRectangle aDestRect; + + // calc effective transformation for bitmap + const ::basegfx::B2DRectangle aSrcRect( 0, 0, + aBmpSize.Width(), + aBmpSize.Height() ); + ::canvas::tools::calcTransformedRectBounds( aDestRect, + aSrcRect, + rTransform ); + + // re-center bitmap, such that it's left, top border is + // aligned with (0,0). The method takes the given + // rectangle, and calculates a transformation that maps + // this rectangle unscaled to the origin. + ::basegfx::B2DHomMatrix aLocalTransform; + ::canvas::tools::calcRectToOriginTransform( aLocalTransform, + aSrcRect, + rTransform ); + + return vcl::bitmap::CanvasTransformBitmap(rBitmap, rTransform, aDestRect, aLocalTransform); + } + + void SetDefaultDeviceAntiAliasing( OutputDevice* pDevice ) + { +#if defined( MACOSX ) + // use AA on VCLCanvas for Mac + pDevice->SetAntialiasing( AntialiasingFlags::Enable | pDevice->GetAntialiasing() ); +#else + // switch off AA for WIN32 and UNIX, the VCLCanvas does not look good with it and + // is not required to do AA. It would need to be adapted to use it correctly + // (especially gradient painting). This will need extra work. + if( SkiaHelper::isVCLSkiaEnabled()) // But Skia handles AA fine. + pDevice->SetAntialiasing( AntialiasingFlags::Enable | pDevice->GetAntialiasing() ); + else + pDevice->SetAntialiasing(pDevice->GetAntialiasing() & ~AntialiasingFlags::Enable); +#endif + } + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/vcl/impltools.hxx b/canvas/source/vcl/impltools.hxx new file mode 100644 index 0000000000..030d5341b5 --- /dev/null +++ b/canvas/source/vcl/impltools.hxx @@ -0,0 +1,183 @@ +/* -*- 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 . + */ + +#pragma once + +#include <osl/mutex.hxx> + +#include <vcl/svapp.hxx> +#include <vcl/outdev.hxx> + +#include <basegfx/polygon/b2dpolypolygon.hxx> + +#include <com/sun/star/uno/Reference.hxx> + +#include "outdevprovider.hxx" + + +class OutputDevice; +class Point; +class Size; + +namespace basegfx +{ + namespace matrix + { + class B2DHomMatrix; + } +} + +namespace com::sun::star::awt +{ + struct Point; + struct Size; + struct Rectangle; +} + +namespace com::sun::star::drawing +{ + struct HomogenMatrix3; +} + +namespace com::sun::star::geometry +{ + struct RealPoint2D; + struct RealSize2D; + struct RealRectangle2D; + struct Matrix2D; +} + +namespace com::sun::star::rendering +{ + struct RenderState; + struct ViewState; + class XBitmap; +} + + +namespace vclcanvas +{ + namespace tools + { + ::BitmapEx + bitmapExFromXBitmap( const css::uno::Reference< + css::rendering::XBitmap >& ); + + /** Setup VCL font and output position + + @returns false, if no text output should happen + */ + bool setupFontTransform( ::Point& o_rPoint, + vcl::Font& io_rVCLFont, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState, + ::OutputDevice const & rOutDev ); + + void setupFontWidth(const css::geometry::Matrix2D& rFontMatrix, + vcl::Font& rFont, + OutputDevice& rOutDev); + + /** Predicate, to determine whether polygon is actually an axis-aligned rectangle + + @return true, if the polygon is a rectangle. + */ + bool isRectangle( const ::tools::PolyPolygon& rPolyPoly ); + + + // Little helper to encapsulate locking into policy class + class LocalGuard + { + public: + LocalGuard() : + aSolarGuard() + { + } + + /// To be compatible with CanvasBase mutex concept + explicit LocalGuard( const ::osl::Mutex& ) : + aSolarGuard() + { + } + + private: + SolarMutexGuard aSolarGuard; + }; + + class OutDevStateKeeper + { + public: + explicit OutDevStateKeeper( OutputDevice& rOutDev ) : + mpOutDev( &rOutDev ), + mbMappingWasEnabled( mpOutDev->IsMapModeEnabled() ), + mnAntiAliasing( mpOutDev->GetAntialiasing() ) + { + init(); + } + + explicit OutDevStateKeeper( const OutDevProviderSharedPtr& rOutDev ) : + mpOutDev( rOutDev ? &(rOutDev->getOutDev()) : nullptr ), + mbMappingWasEnabled( mpOutDev && mpOutDev->IsMapModeEnabled() ), + mnAntiAliasing( mpOutDev ? mpOutDev->GetAntialiasing() : AntialiasingFlags::NONE ) + { + init(); + } + + ~OutDevStateKeeper() + { + if( mpOutDev ) + { + mpOutDev->EnableMapMode( mbMappingWasEnabled ); + mpOutDev->SetAntialiasing( mnAntiAliasing ); + + mpOutDev->Pop(); + } + } + + private: + void init() + { + if( mpOutDev ) + { + mpOutDev->Push(); + mpOutDev->EnableMapMode(false); + mpOutDev->SetAntialiasing( AntialiasingFlags::Enable ); + } + } + + VclPtr<OutputDevice> mpOutDev; + const bool mbMappingWasEnabled; + const AntialiasingFlags mnAntiAliasing; + }; + + ::Point mapRealPoint2D( const css::geometry::RealPoint2D& rPoint, + const css::rendering::ViewState& rViewState, + const css::rendering::RenderState& rRenderState ); + + ::tools::PolyPolygon mapPolyPolygon( const ::basegfx::B2DPolyPolygon& rPoly, + const css::rendering::ViewState& rViewState, + const css::rendering::RenderState& rRenderState ); + + ::BitmapEx transformBitmap( const BitmapEx& rBitmap, + const ::basegfx::B2DHomMatrix& rTransform ); + + void SetDefaultDeviceAntiAliasing( OutputDevice* pDevice ); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/vcl/outdevholder.hxx b/canvas/source/vcl/outdevholder.hxx new file mode 100644 index 0000000000..9679687c43 --- /dev/null +++ b/canvas/source/vcl/outdevholder.hxx @@ -0,0 +1,50 @@ +/* -*- 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 . + */ + +#pragma once + +#include <vcl/outdev.hxx> + +#include "outdevprovider.hxx" + +namespace vclcanvas +{ +class OutDevHolder : public OutDevProvider +{ +public: + OutDevHolder(const OutDevHolder&) = delete; + const OutDevHolder& operator=(const OutDevHolder&) = delete; + + explicit OutDevHolder(OutputDevice& rOutDev) + : mrOutDev(rOutDev) + { + } + +private: + virtual OutputDevice& getOutDev() override { return mrOutDev; } + virtual const OutputDevice& getOutDev() const override { return mrOutDev; } + + // TODO(Q2): Lifetime issue. This _only_ works reliably, + // if disposing the Canvas correctly disposes all + // entities which hold this pointer. + OutputDevice& mrOutDev; +}; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/vcl/outdevprovider.hxx b/canvas/source/vcl/outdevprovider.hxx new file mode 100644 index 0000000000..668212b9db --- /dev/null +++ b/canvas/source/vcl/outdevprovider.hxx @@ -0,0 +1,50 @@ +/* -*- 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 . + */ + +#pragma once + +#include <memory> + +class OutputDevice; + +namespace vclcanvas +{ + /* Definition of OutDevProvider interface */ + + /** Implementers of this interface provide the CanvasHelper + with its OutputDevice. + + This additional level of indirection was necessary, as the + OutputDevice is not an interface. There had to be a mechanism + to detect the moment when an OutputDevice is rendered to + (e.g. for the BitmapBackBuffer). + */ + class OutDevProvider + { + public: + virtual ~OutDevProvider() {} + + virtual OutputDevice& getOutDev() = 0; + virtual const OutputDevice& getOutDev() const = 0; + }; + + typedef std::shared_ptr< OutDevProvider > OutDevProviderSharedPtr; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/vcl/repainttarget.hxx b/canvas/source/vcl/repainttarget.hxx new file mode 100644 index 0000000000..b8bac4ab92 --- /dev/null +++ b/canvas/source/vcl/repainttarget.hxx @@ -0,0 +1,52 @@ +/* -*- 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 . + */ + +#pragma once + +#include "cachedbitmap.hxx" + +class Point; +class Size; +class GraphicAttr; + +namespace vclcanvas +{ + /* Definition of RepaintTarget interface */ + + /** Target interface for XCachedPrimitive implementations + + This interface must be implemented on all canvas + implementations that hand out XCachedPrimitives + */ + class SAL_LOPLUGIN_ANNOTATE("crosscast") RepaintTarget + { + public: + virtual ~RepaintTarget() {} + + // call this when a bitmap is repainted + virtual bool repaint( const GraphicObjectSharedPtr& rGrf, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState, + const ::Point& rPt, + const ::Size& rSz, + const GraphicAttr& rAttr ) const = 0; + }; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/vcl/sprite.hxx b/canvas/source/vcl/sprite.hxx new file mode 100644 index 0000000000..30ab15aaf0 --- /dev/null +++ b/canvas/source/vcl/sprite.hxx @@ -0,0 +1,65 @@ +/* -*- 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 . + */ + +#pragma once + +#include <base/sprite.hxx> + +class OutputDevice; + +namespace vclcanvas +{ + /** Specialization of ::canvas::Sprite interface, to also provide + redraw methods. + */ + class Sprite : public ::canvas::Sprite + { + public: + + /** Redraw sprite at the stored position. + + @param bBufferedUpdate + When true, the redraw does <em>not</em> happen directly on + the front buffer, but within a VDev. Used to speed up + drawing. + */ + virtual void redraw( OutputDevice& rOutDev, + bool bBufferedUpdate ) const = 0; + + /** Redraw sprite at the given position. + + @param rPos + Output position of the sprite. Overrides the sprite's own + output position. + + @param bBufferedUpdate + When true, the redraw does <em>not</em> happen directly on + the front buffer, but within a VDev. Used to speed up + drawing. + */ + virtual void redraw( OutputDevice& rOutDev, + const ::basegfx::B2DPoint& rPos, + bool bBufferedUpdate ) const = 0; + + protected: + ~Sprite() {} + }; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/vcl/spritecanvas.cxx b/canvas/source/vcl/spritecanvas.cxx new file mode 100644 index 0000000000..cbe0fa8dc7 --- /dev/null +++ b/canvas/source/vcl/spritecanvas.cxx @@ -0,0 +1,186 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> +#include <sal/log.hxx> + +#include <com/sun/star/awt/XTopWindow.hpp> +#include <com/sun/star/lang/NoSupportException.hpp> +#include <cppuhelper/supportsservice.hxx> +#include <comphelper/diagnose_ex.hxx> + +#include "spritecanvas.hxx" +#include "outdevholder.hxx" +#include "windowoutdevholder.hxx" + + +using namespace ::com::sun::star; + +namespace vclcanvas +{ + SpriteCanvas::SpriteCanvas( const uno::Sequence< uno::Any >& aArguments, + const uno::Reference< uno::XComponentContext >& /*rxContext*/ ) : + maArguments(aArguments) + { + } + + void SpriteCanvas::initialize() + { + SolarMutexGuard aGuard; + + // #i64742# Only call initialize when not in probe mode + if( !maArguments.hasElements() ) + return; + + SAL_INFO("canvas.vcl", "SpriteCanvas created" ); + + // add our own property to GraphicDevice + maPropHelper.addProperties( + ::canvas::PropertySetHelper::MakeMap + ("UnsafeScrolling", + [this]() { return this->maCanvasHelper.isUnsafeScrolling(); }, + [this](css::uno::Any const& aAny) mutable { this->maCanvasHelper.enableUnsafeScrolling(aAny); } ) + ("SpriteBounds", + [this]() { return this->maCanvasHelper.isSpriteBounds(); }, + [this](css::uno::Any const& aAny) mutable { this->maCanvasHelper.enableSpriteBounds(aAny); } )); + + SAL_INFO("canvas.vcl", "VCLSpriteCanvas::initialize called" ); + + ENSURE_ARG_OR_THROW( maArguments.hasElements(), + "VCLSpriteCanvas::initialize: wrong number of arguments" ); + + /* maArguments: + 0: ptr to creating instance (Window or VirtualDevice) + 1: current bounds of creating instance + 2: bool, denoting always on top state for Window (always false for VirtualDevice) + 3: XWindow for creating Window (or empty for VirtualDevice) + 4: SystemGraphicsData as a streamed Any + */ + ENSURE_ARG_OR_THROW( maArguments.getLength() >= 4 && + maArguments[0].getValueTypeClass() == uno::TypeClass_HYPER && + maArguments[3].getValueTypeClass() == uno::TypeClass_INTERFACE, + "VCLSpriteCanvas::initialize: wrong number of arguments, or wrong types" ); + + sal_Int64 nPtr = 0; + maArguments[0] >>= nPtr; + + OutputDevice* pOutDev = reinterpret_cast<OutputDevice*>(nPtr); + if( !pOutDev ) + throw lang::NoSupportException("Passed OutDev invalid!", nullptr); + + uno::Reference< awt::XWindow > xParentWindow; + maArguments[3] >>= xParentWindow; + + OutDevProviderSharedPtr pOutDevProvider; + if( xParentWindow.is()) + pOutDevProvider = std::make_shared<WindowOutDevHolder>(xParentWindow); + else + pOutDevProvider = std::make_shared<OutDevHolder>(*pOutDev); + + // setup helper + maDeviceHelper.init( pOutDevProvider ); + setWindow( xParentWindow.is() + ? uno::Reference<awt::XWindow2>(xParentWindow, uno::UNO_QUERY_THROW) + : uno::Reference<awt::XWindow2>()); + maCanvasHelper.init( maDeviceHelper.getBackBuffer(), + *this, + maRedrawManager, + false, // no OutDev state preservation + false ); // no alpha on surface + + maArguments.realloc(0); + } + + SpriteCanvas::~SpriteCanvas() + { + SAL_INFO("canvas.vcl", "SpriteCanvas destroyed" ); + } + + + void SpriteCanvas::disposeThis() + { + SolarMutexGuard aGuard; + + // forward to parent + SpriteCanvasBaseT::disposeThis(); + } + + sal_Bool SAL_CALL SpriteCanvas::showBuffer( sal_Bool bUpdateAll ) + { + return updateScreen( bUpdateAll ); + } + + sal_Bool SAL_CALL SpriteCanvas::switchBuffer( sal_Bool bUpdateAll ) + { + return updateScreen( bUpdateAll ); + } + + sal_Bool SAL_CALL SpriteCanvas::updateScreen( sal_Bool bUpdateAll ) + { + SolarMutexGuard aGuard; + + // avoid repaints on hidden window (hidden: not mapped to + // screen). Return failure, since the screen really has _not_ + // been updated (caller should try again later) + return mbIsVisible && maCanvasHelper.updateScreen(bUpdateAll, + mbSurfaceDirty); + } + + OUString SAL_CALL SpriteCanvas::getServiceName( ) + { + return "com.sun.star.rendering.SpriteCanvas.VCL"; + } + + // XServiceInfo + css::uno::Sequence<OUString> SpriteCanvas::getSupportedServiceNames() + { + return { SpriteCanvas::getServiceName() }; + } + OUString SpriteCanvas::getImplementationName() + { + return "com.sun.star.comp.rendering.SpriteCanvas.VCL"; + } + sal_Bool SpriteCanvas::supportsService(const OUString& sServiceName) + { + return cppu::supportsService(this, sServiceName); + } + + bool SpriteCanvas::repaint( const GraphicObjectSharedPtr& rGrf, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState, + const ::Point& rPt, + const ::Size& rSz, + const GraphicAttr& rAttr ) const + { + SolarMutexGuard aGuard; + + return maCanvasHelper.repaint( rGrf, viewState, renderState, rPt, rSz, rAttr ); + } +} + +extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* +com_sun_star_comp_rendering_SpriteCanvas_VCL_get_implementation( + css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const& args) +{ + rtl::Reference<vclcanvas::SpriteCanvas> p = new vclcanvas::SpriteCanvas(args, context); + p->initialize(); + return cppu::acquire(p.get()); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/vcl/spritecanvas.hxx b/canvas/source/vcl/spritecanvas.hxx new file mode 100644 index 0000000000..8f7c76880f --- /dev/null +++ b/canvas/source/vcl/spritecanvas.hxx @@ -0,0 +1,163 @@ +/* -*- 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 . + */ + +#pragma once + +#include <rtl/ref.hxx> + +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/lang/XServiceName.hpp> +#include <com/sun/star/awt/XWindowListener.hpp> +#include <com/sun/star/util/XUpdatable.hpp> +#include <com/sun/star/rendering/XSpriteCanvas.hpp> +#include <com/sun/star/rendering/XIntegerBitmap.hpp> +#include <com/sun/star/rendering/XGraphicDevice.hpp> +#include <com/sun/star/rendering/XBufferController.hpp> + +#include <cppuhelper/compbase.hxx> +#include <comphelper/uno3.hxx> + +#include <base/spritecanvasbase.hxx> +#include <base/spritesurface.hxx> +#include <base/disambiguationhelper.hxx> +#include <base/bufferedgraphicdevicebase.hxx> + +#include "spritecanvashelper.hxx" +#include "impltools.hxx" +#include "spritedevicehelper.hxx" +#include "repainttarget.hxx" + + +namespace vclcanvas +{ + typedef ::cppu::WeakComponentImplHelper< css::rendering::XSpriteCanvas, + css::rendering::XIntegerBitmap, + css::rendering::XGraphicDevice, + css::lang::XMultiServiceFactory, + css::rendering::XBufferController, + css::awt::XWindowListener, + css::util::XUpdatable, + css::beans::XPropertySet, + css::lang::XServiceName, + css::lang::XServiceInfo > WindowGraphicDeviceBase_Base; + typedef ::canvas::BufferedGraphicDeviceBase< ::canvas::DisambiguationHelper< WindowGraphicDeviceBase_Base >, + SpriteDeviceHelper, + tools::LocalGuard, + ::cppu::OWeakObject > SpriteCanvasBase_Base; + + /** Mixin SpriteSurface + + Have to mixin the SpriteSurface before deriving from + ::canvas::SpriteCanvasBase, as this template should already + implement some of those interface methods. + + The reason why this appears kinda convoluted is the fact that + we cannot specify non-IDL types as WeakComponentImplHelper + template args, and furthermore, don't want to derive + ::canvas::SpriteCanvasBase directly from + ::canvas::SpriteSurface (because derivees of + ::canvas::SpriteCanvasBase have to explicitly forward the + XInterface methods (e.g. via DECLARE_UNO3_AGG_DEFAULTS) + anyway). Basically, ::canvas::CanvasCustomSpriteBase should + remain a base class that provides implementation, not to + enforce any specific interface on its derivees. + */ + class SpriteCanvasBaseSpriteSurface_Base : public SpriteCanvasBase_Base, + public ::canvas::SpriteSurface + { + }; + + typedef ::canvas::SpriteCanvasBase< SpriteCanvasBaseSpriteSurface_Base, + SpriteCanvasHelper, + tools::LocalGuard, + ::cppu::OWeakObject > SpriteCanvasBaseT; + + /** Product of this component's factory. + + The SpriteCanvas object combines the actual Window canvas with + the XGraphicDevice interface. This is because there's a + one-to-one relation between them, anyway, since each window + can have exactly one canvas and one associated + XGraphicDevice. And to avoid messing around with circular + references, this is implemented as one single object. + */ + class SpriteCanvas : public SpriteCanvasBaseT, + public RepaintTarget + { + public: + SpriteCanvas( const css::uno::Sequence< + css::uno::Any >& aArguments, + const css::uno::Reference< + css::uno::XComponentContext >& rxContext ); + + void initialize(); + + /// For resource tracking + virtual ~SpriteCanvas() override; + + /// Dispose all internal references + virtual void disposeThis() override; + + // Forwarding the XComponent implementation to the + // cppu::ImplHelper templated base + // Classname Base doing refcounting Base implementing the XComponent interface + // | | | + // V V V + DECLARE_UNO3_XCOMPONENT_AGG_DEFAULTS( SpriteCanvas, WindowGraphicDeviceBase_Base, ::cppu::WeakComponentImplHelperBase ) + + // XBufferController (partial) + virtual sal_Bool SAL_CALL showBuffer( sal_Bool bUpdateAll ) override; + virtual sal_Bool SAL_CALL switchBuffer( sal_Bool bUpdateAll ) override; + + // XSpriteCanvas (partial) + virtual sal_Bool SAL_CALL updateScreen( sal_Bool bUpdateAll ) override; + + // XServiceName + virtual OUString SAL_CALL getServiceName( ) override; + + // XServiceInfo + virtual css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames() override; + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService(const OUString&) override; + + // RepaintTarget + virtual bool repaint( const GraphicObjectSharedPtr& rGrf, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState, + const ::Point& rPt, + const ::Size& rSz, + const GraphicAttr& rAttr ) const override; + + /// Get backbuffer for this canvas + OutDevProviderSharedPtr const & getFrontBuffer() const { return maDeviceHelper.getOutDev(); } + /// Get window for this canvas + BackBufferSharedPtr const & getBackBuffer() const { return maDeviceHelper.getBackBuffer(); } + + private: + css::uno::Sequence< css::uno::Any > maArguments; + }; + + typedef ::rtl::Reference< SpriteCanvas > SpriteCanvasRef; + typedef ::rtl::Reference< SpriteCanvas > DeviceRef; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/vcl/spritecanvashelper.cxx b/canvas/source/vcl/spritecanvashelper.cxx new file mode 100644 index 0000000000..37f70a3642 --- /dev/null +++ b/canvas/source/vcl/spritecanvashelper.cxx @@ -0,0 +1,661 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> +#include <sal/log.hxx> + +#include <boost/cast.hpp> + +#include <basegfx/range/b2drectangle.hxx> +#include <rtl/math.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <vcl/canvastools.hxx> +#include <vcl/outdev.hxx> +#include <vcl/window.hxx> + +#include <canvas/canvastools.hxx> + +#include "canvascustomsprite.hxx" +#include "spritecanvashelper.hxx" +#include "spritecanvas.hxx" + +using namespace ::com::sun::star; + +#define FPS_BOUNDS ::tools::Rectangle(0,0,130,90) +#define INFO_COLOR COL_RED + +namespace vclcanvas +{ + namespace + { + /** Sprite redraw at original position + + Used to repaint the whole canvas (background and all + sprites) + */ + void spriteRedraw( OutputDevice& rOutDev, + const ::canvas::Sprite::Reference& rSprite ) + { + // downcast to derived vclcanvas::Sprite interface, which + // provides the actual redraw methods. + ::boost::polymorphic_downcast< Sprite* >(rSprite.get())->redraw(rOutDev, + true); + } + + double calcNumPixel( const ::canvas::Sprite::Reference& rSprite ) + { + const ::basegfx::B2DVector& rSize( + ::boost::polymorphic_downcast< Sprite* >(rSprite.get())->getSizePixel() ); + + return rSize.getX() * rSize.getY(); + } + + void repaintBackground( OutputDevice& rOutDev, + OutputDevice const & rBackBuffer, + const ::basegfx::B2DRange& rArea ) + { + const ::Point& rPos( vcl::unotools::pointFromB2DPoint( rArea.getMinimum()) ); + const ::Size& rSize( vcl::unotools::sizeFromB2DSize( rArea.getRange()) ); + + rOutDev.DrawOutDev( rPos, rSize, rPos, rSize, rBackBuffer ); + } + + void opaqueUpdateSpriteArea( const ::canvas::Sprite::Reference& rSprite, + OutputDevice& rOutDev, + const ::basegfx::B2IRange& rArea ) + { + const ::tools::Rectangle& rRequestedArea( + vcl::unotools::rectangleFromB2IRectangle( rArea ) ); + + // clip output to actual update region (otherwise a) + // wouldn't save much render time, and b) will clutter + // scrolled sprite content outside this area) + rOutDev.EnableMapMode( false ); + rOutDev.SetAntialiasing( AntialiasingFlags::Enable ); + rOutDev.SetClipRegion(vcl::Region(rRequestedArea)); + + // repaint affected sprite directly to output device (at + // the actual screen output position) + ::boost::polymorphic_downcast< Sprite* >( + rSprite.get() )->redraw( rOutDev, + false ); // rendering + // directly to + // frontbuffer + } + + void renderInfoText( OutputDevice& rOutDev, + const OUString& rStr, + const Point& rPos ) + { + vcl::Font aVCLFont; + aVCLFont.SetFontHeight( 20 ); + aVCLFont.SetColor( INFO_COLOR ); + + rOutDev.SetTextAlign(ALIGN_TOP); + rOutDev.SetTextColor( INFO_COLOR ); + rOutDev.SetFont( aVCLFont ); + + rOutDev.DrawText( rPos, rStr ); + } + + } + + SpriteCanvasHelper::SpriteCanvasHelper() : + mpRedrawManager( nullptr ), + mpOwningSpriteCanvas( nullptr ), + maVDev(VclPtr<VirtualDevice>::Create()), + mbShowFrameInfo( false ), + mbShowSpriteBounds( false ), + mbIsUnsafeScrolling( false ) + { +#if OSL_DEBUG_LEVEL > 0 + // inverse defaults for verbose debug mode + mbShowFrameInfo = true; + // this looks like drawing errors, enable only if explicitly asked for + static bool enableShowSpriteBounds = getenv("CANVAS_SPRITE_BOUNDS") != nullptr; + mbShowSpriteBounds = enableShowSpriteBounds; +#endif + } + + SpriteCanvasHelper::~SpriteCanvasHelper() + { + SolarMutexGuard aGuard; + maVDev.disposeAndClear(); + } + + void SpriteCanvasHelper::init( const OutDevProviderSharedPtr& rOutDev, + SpriteCanvas& rOwningSpriteCanvas, + ::canvas::SpriteRedrawManager& rManager, + bool bProtect, + bool bHaveAlpha ) + { + mpOwningSpriteCanvas = &rOwningSpriteCanvas; + mpRedrawManager = &rManager; + + CanvasHelper::init(rOwningSpriteCanvas,rOutDev,bProtect,bHaveAlpha); + } + + void SpriteCanvasHelper::disposing() + { + mpRedrawManager = nullptr; + mpOwningSpriteCanvas = nullptr; + + // forward to base + CanvasHelper::disposing(); + } + + uno::Reference< rendering::XAnimatedSprite > SpriteCanvasHelper::createSpriteFromAnimation( + const uno::Reference< rendering::XAnimation >& ) + { + return uno::Reference< rendering::XAnimatedSprite >(); + } + + uno::Reference< rendering::XAnimatedSprite > SpriteCanvasHelper::createSpriteFromBitmaps( + const uno::Sequence< uno::Reference< rendering::XBitmap > >& , + sal_Int8 ) + { + return uno::Reference< rendering::XAnimatedSprite >(); + } + + uno::Reference< rendering::XCustomSprite > SpriteCanvasHelper::createCustomSprite( const geometry::RealSize2D& spriteSize ) + { + if( !mpRedrawManager || !mpDevice ) + return uno::Reference< rendering::XCustomSprite >(); // we're disposed + + return uno::Reference< rendering::XCustomSprite >( + new CanvasCustomSprite( spriteSize, + *mpDevice, + mpOwningSpriteCanvas, + mpOwningSpriteCanvas->getFrontBuffer(), + mbShowSpriteBounds ) ); + } + + uno::Reference< rendering::XSprite > SpriteCanvasHelper::createClonedSprite( const uno::Reference< rendering::XSprite >& ) + { + return uno::Reference< rendering::XSprite >(); + } + + bool SpriteCanvasHelper::updateScreen( bool bUpdateAll, + bool& io_bSurfaceDirty ) + { + if( !mpRedrawManager || + !mpOwningSpriteCanvas || + !mpOwningSpriteCanvas->getFrontBuffer() || + !mpOwningSpriteCanvas->getBackBuffer() ) + { + return false; // disposed, or otherwise dysfunctional + } + + // commit to backbuffer + flush(); + + OutputDevice& rOutDev( mpOwningSpriteCanvas->getFrontBuffer()->getOutDev() ); + BackBufferSharedPtr pBackBuffer( mpOwningSpriteCanvas->getBackBuffer() ); + OutputDevice& rBackOutDev( pBackBuffer->getOutDev() ); + + // actual OutputDevice is a shared resource - restore its + // state when done. + tools::OutDevStateKeeper aStateKeeper( rOutDev ); + + const Size aOutDevSize( rBackOutDev.GetOutputSizePixel() ); + const Point aEmptyPoint(0,0); + + vcl::Window* pTargetWindow = nullptr; + if( rOutDev.GetOutDevType() == OUTDEV_WINDOW ) + { + pTargetWindow = rOutDev.GetOwnerWindow(); // TODO(Q3): Evil downcast. + + // we're double-buffered, thus no need for paint area-limiting + // clips. besides that, will interfere with animations (as for + // Window-invalidate repaints, only parts of the window will + // be redrawn otherwise) + const vcl::Region aFullWindowRegion( ::tools::Rectangle(aEmptyPoint, + aOutDevSize) ); + pTargetWindow->ExpandPaintClipRegion(aFullWindowRegion); + } + + // TODO(P1): Might be worthwhile to track areas of background + // changes, too. + if( !bUpdateAll && !io_bSurfaceDirty ) + { + if( mbShowFrameInfo ) + { + // also repaint background below frame counter (fake + // that as a sprite vanishing in this area) + mpRedrawManager->updateSprite( ::canvas::Sprite::Reference(), + ::basegfx::B2DPoint(), + ::basegfx::B2DRectangle( 0.0, 0.0, + FPS_BOUNDS.Right(), + FPS_BOUNDS.Bottom() ) ); + } + + // background has not changed, so we're free to optimize + // repaint to areas where a sprite has changed + + // process each independent area of overlapping sprites + // separately. + mpRedrawManager->forEachSpriteArea( *this ); + } + else + { + // background has changed, so we currently have no choice + // but repaint everything (or caller requested that) + + maVDev->SetOutputSizePixel( aOutDevSize ); + maVDev->EnableMapMode( false ); + maVDev->DrawOutDev( aEmptyPoint, aOutDevSize, + aEmptyPoint, aOutDevSize, + rBackOutDev ); + + // repaint all active sprites on top of background into + // VDev. + OutputDevice& rTmpOutDev( *maVDev ); + mpRedrawManager->forEachSprite( + [&rTmpOutDev]( const ::canvas::Sprite::Reference& rSprite ) + { spriteRedraw( rTmpOutDev, rSprite ); } + ); + + // flush to screen + rOutDev.EnableMapMode( false ); + rOutDev.SetAntialiasing( AntialiasingFlags::Enable ); + rOutDev.SetClipRegion(); + rOutDev.DrawOutDev( aEmptyPoint, aOutDevSize, + aEmptyPoint, aOutDevSize, + *maVDev ); + } + + // change record vector must be cleared, for the next turn of + // rendering and sprite changing + mpRedrawManager->clearChangeRecords(); + + io_bSurfaceDirty = false; + + if( mbShowFrameInfo ) + { + renderFrameCounter( rOutDev ); + renderSpriteCount( rOutDev ); + renderMemUsage( rOutDev ); + } + +#if OSL_DEBUG_LEVEL > 0 + static ::canvas::tools::ElapsedTime aElapsedTime; + + // log time immediately after surface flip + SAL_INFO("canvas.vcl", "SpriteCanvasHelper::updateScreen(): flip done at " << + aElapsedTime.getElapsedTime() ); +#endif + + // sync output with screen, to ensure that we don't queue up + // render requests (calling code might rely on timing, + // i.e. assume that things are visible on screen after + // updateScreen() returns). + if( pTargetWindow ) + { + // commit to screen + pTargetWindow->GetOutDev()->Flush(); + } + + return true; + } + + void SpriteCanvasHelper::backgroundPaint( const ::basegfx::B2DRange& rUpdateRect ) + { + ENSURE_OR_THROW( mpOwningSpriteCanvas && + mpOwningSpriteCanvas->getBackBuffer() && + mpOwningSpriteCanvas->getFrontBuffer(), + "SpriteCanvasHelper::backgroundPaint(): NULL device pointer " ); + + OutputDevice& rOutDev( mpOwningSpriteCanvas->getFrontBuffer()->getOutDev() ); + BackBufferSharedPtr pBackBuffer( mpOwningSpriteCanvas->getBackBuffer() ); + OutputDevice& rBackOutDev( pBackBuffer->getOutDev() ); + + repaintBackground( rOutDev, rBackOutDev, rUpdateRect ); + } + + void SpriteCanvasHelper::scrollUpdate( const ::basegfx::B2DRange& rMoveStart, + const ::basegfx::B2DRange& rMoveEnd, + const ::canvas::SpriteRedrawManager::UpdateArea& rUpdateArea ) + { + ENSURE_OR_THROW( mpOwningSpriteCanvas && + mpOwningSpriteCanvas->getBackBuffer() && + mpOwningSpriteCanvas->getFrontBuffer(), + "SpriteCanvasHelper::scrollUpdate(): NULL device pointer " ); + + OutputDevice& rOutDev( mpOwningSpriteCanvas->getFrontBuffer()->getOutDev() ); + BackBufferSharedPtr pBackBuffer( mpOwningSpriteCanvas->getBackBuffer() ); + OutputDevice& rBackOutDev( pBackBuffer->getOutDev() ); + + const Size& rTargetSizePixel( rOutDev.GetOutputSizePixel() ); + const ::basegfx::B2IRange aOutputBounds( 0,0, + rTargetSizePixel.Width(), + rTargetSizePixel.Height() ); + + // round rectangles to integer pixel. Note: have to be + // extremely careful here, to avoid off-by-one errors for + // the destination area: otherwise, the next scroll update + // would copy pixel that are not supposed to be part of + // the sprite. + ::basegfx::B2IRange aSourceRect( + ::canvas::tools::spritePixelAreaFromB2DRange( rMoveStart ) ); + const ::basegfx::B2IRange& rDestRect( + ::canvas::tools::spritePixelAreaFromB2DRange( rMoveEnd ) ); + ::basegfx::B2IPoint aDestPos( rDestRect.getMinimum() ); + + std::vector< ::basegfx::B2IRange > aUnscrollableAreas; + + // Since strictly speaking, this scroll algorithm is plain + // buggy, the scrolled area might actually lie _below_ another + // window - we've made this feature configurable via + // mbIsUnsafeScrolling. + + // clip to output bounds (cannot properly scroll stuff + // _outside_ our screen area) + if( !mbIsUnsafeScrolling || + !::canvas::tools::clipScrollArea( aSourceRect, + aDestPos, + aUnscrollableAreas, + aOutputBounds ) ) + { + // fully clipped scroll area: cannot simply scroll + // then. Perform normal opaque update (can use that, since + // one of the preconditions for scrollable update is + // opaque sprite content) + + // repaint all affected sprites directly to output device + for( const auto& rComponent : rUpdateArea.maComponentList ) + { + const ::canvas::Sprite::Reference& rSprite( rComponent.second.getSprite() ); + + if( rSprite.is() ) + ::boost::polymorphic_downcast< Sprite* >( + rSprite.get() )->redraw( rOutDev, + false ); + } + } + else + { + // scroll rOutDev content + rOutDev.CopyArea( vcl::unotools::pointFromB2IPoint( aDestPos ), + vcl::unotools::pointFromB2IPoint( aSourceRect.getMinimum() ), + // TODO(Q2): use numeric_cast to check range + ::Size( static_cast<sal_Int32>(aSourceRect.getRange().getX()), + static_cast<sal_Int32>(aSourceRect.getRange().getY()) ) ); + + const ::canvas::SpriteRedrawManager::SpriteConnectedRanges::ComponentListType::const_iterator + aFirst( rUpdateArea.maComponentList.begin() ); + + ENSURE_OR_THROW( aFirst->second.getSprite().is(), + "VCLCanvas::scrollUpdate(): no sprite" ); + + // repaint uncovered areas from sprite. Need to actually + // clip here, since we're only repainting _parts_ of the + // sprite + rOutDev.Push( vcl::PushFlags::CLIPREGION ); + + for( const auto& rArea : aUnscrollableAreas ) + opaqueUpdateSpriteArea( aFirst->second.getSprite(), + rOutDev, rArea ); + + rOutDev.Pop(); + } + + // repaint uncovered areas from backbuffer - take the + // _rounded_ rectangles from above, to have the update + // consistent with the scroll above. + std::vector< ::basegfx::B2DRange > aUncoveredAreas; + ::basegfx::computeSetDifference( aUncoveredAreas, + rUpdateArea.maTotalBounds, + ::basegfx::B2DRange( rDestRect ) ); + + for( const auto& rArea : aUncoveredAreas ) + repaintBackground( rOutDev, rBackOutDev, rArea ); + } + + void SpriteCanvasHelper::opaqueUpdate( SAL_UNUSED_PARAMETER const ::basegfx::B2DRange&, + const std::vector< ::canvas::Sprite::Reference >& rSortedUpdateSprites ) + { + ENSURE_OR_THROW( mpOwningSpriteCanvas && + mpOwningSpriteCanvas->getBackBuffer() && + mpOwningSpriteCanvas->getFrontBuffer(), + "SpriteCanvasHelper::opaqueUpdate(): NULL device pointer " ); + + OutputDevice& rOutDev( mpOwningSpriteCanvas->getFrontBuffer()->getOutDev() ); + + // no need to clip output to actual update region - there will + // always be ALL sprites contained in the rectangular update + // area contained in rTotalArea (that's the way + // B2DConnectedRanges work). If rTotalArea appears to be + // smaller than the sprite - then this sprite carries a clip, + // and the update will be constrained to that rect. + + // repaint all affected sprites directly to output device + for( const auto& rSprite : rSortedUpdateSprites ) + { + if( rSprite.is() ) + ::boost::polymorphic_downcast< Sprite* >( + rSprite.get() )->redraw( rOutDev, + false ); + } + } + + void SpriteCanvasHelper::genericUpdate( const ::basegfx::B2DRange& rRequestedArea, + const std::vector< ::canvas::Sprite::Reference >& rSortedUpdateSprites ) + { + ENSURE_OR_THROW( mpOwningSpriteCanvas && + mpOwningSpriteCanvas->getBackBuffer() && + mpOwningSpriteCanvas->getFrontBuffer(), + "SpriteCanvasHelper::genericUpdate(): NULL device pointer " ); + + OutputDevice& rOutDev( mpOwningSpriteCanvas->getFrontBuffer()->getOutDev() ); + BackBufferSharedPtr pBackBuffer( mpOwningSpriteCanvas->getBackBuffer() ); + OutputDevice& rBackOutDev( pBackBuffer->getOutDev() ); + + // limit size of update VDev to target outdev's size + const Size& rTargetSizePixel( rOutDev.GetOutputSizePixel() ); + + // round output position towards zero. Don't want to truncate + // a fraction of a sprite pixel... Clip position at origin, + // otherwise, truncation of size below might leave visible + // areas uncovered by VDev. + const ::Point aOutputPosition( + std::max( sal_Int32( 0 ), + static_cast< sal_Int32 >(rRequestedArea.getMinX()) ), + std::max( sal_Int32( 0 ), + static_cast< sal_Int32 >(rRequestedArea.getMinY()) ) ); + // round output size towards +infty. Don't want to truncate a + // fraction of a sprite pixel... Limit coverage of VDev to + // output device's area (i.e. not only to total size, but to + // cover _only_ the visible parts). + const ::Size aOutputSize( + std::max( sal_Int32( 0 ), + std::min( static_cast< sal_Int32 >(rTargetSizePixel.Width() - aOutputPosition.X()), + ::canvas::tools::roundUp( rRequestedArea.getMaxX() - aOutputPosition.X() ))), + std::max( sal_Int32( 0 ), + std::min( static_cast< sal_Int32 >(rTargetSizePixel.Height() - aOutputPosition.Y()), + ::canvas::tools::roundUp( rRequestedArea.getMaxY() - aOutputPosition.Y() )))); + + // early exit for empty output area. + if( aOutputSize.Width() == 0 && + aOutputSize.Height() == 0 ) + { + return; + } + + const Point aEmptyPoint(0,0); + const Size aCurrOutputSize( maVDev->GetOutputSizePixel() ); + + // adapt maVDev's size to the area that actually needs the + // repaint. + if( aCurrOutputSize.Width() < aOutputSize.Width() || + aCurrOutputSize.Height() < aOutputSize.Height() ) + { + // TODO(P1): Come up with a clever tactic to reduce maVDev + // from time to time. Reduction with threshold (say, if + // maVDev is more than twice too large) is not wise, as + // this might then toggle within the same updateScreen(), + // but for different disjunct sprite areas. + maVDev->SetOutputSizePixel( aOutputSize ); + } + + // paint background + maVDev->EnableMapMode( false ); + maVDev->SetAntialiasing( AntialiasingFlags::Enable ); + maVDev->SetClipRegion(); + maVDev->DrawOutDev( aEmptyPoint, aOutputSize, + aOutputPosition, aOutputSize, + rBackOutDev ); + + // repaint all affected sprites on top of background into + // VDev. + for( const auto& rSprite : rSortedUpdateSprites ) + { + if( rSprite.is() ) + { + Sprite* pSprite = ::boost::polymorphic_downcast< Sprite* >( rSprite.get() ); + + // calc relative sprite position in rUpdateArea (which + // need not be the whole screen!) + const ::basegfx::B2DPoint& rSpriteScreenPos( pSprite->getPosPixel() ); + const ::basegfx::B2DPoint& rSpriteRenderPos( + rSpriteScreenPos - vcl::unotools::b2DPointFromPoint(aOutputPosition) + ); + + pSprite->redraw( *maVDev, rSpriteRenderPos, true ); + } + } + + // flush to screen + rOutDev.EnableMapMode( false ); + rOutDev.SetAntialiasing( AntialiasingFlags::Enable ); + rOutDev.DrawOutDev( aOutputPosition, aOutputSize, + aEmptyPoint, aOutputSize, + *maVDev ); + } + + void SpriteCanvasHelper::renderFrameCounter( OutputDevice& rOutDev ) + { + const double denominator( maLastUpdate.getElapsedTime() ); + maLastUpdate.reset(); + + OUString text( ::rtl::math::doubleToUString( denominator == 0.0 ? 100.0 : 1.0/denominator, + rtl_math_StringFormat_F, + 2,'.',nullptr,' ') ); + + // pad with leading space + while( text.getLength() < 6 ) + text = " " + text; + + text += " fps"; + + renderInfoText( rOutDev, + text, + Point(0, 0) ); + } + + namespace + { + template< typename T > struct Adder + { + typedef void result_type; + + Adder( T& rAdderTarget, + T nIncrement ) : + mpTarget( &rAdderTarget ), + mnIncrement( nIncrement ) + { + } + + void operator()( const ::canvas::Sprite::Reference& ) { *mpTarget += mnIncrement; } + void operator()( T nIncrement ) { *mpTarget += nIncrement; } + + T* mpTarget; + T mnIncrement; + }; + + template< typename T> Adder<T> makeAdder( T& rAdderTarget, + T nIncrement ) + { + return Adder<T>(rAdderTarget, nIncrement); + } + } + + void SpriteCanvasHelper::renderSpriteCount( OutputDevice& rOutDev ) + { + if( !mpRedrawManager ) + return; + + sal_Int32 nCount(0); + + mpRedrawManager->forEachSprite( makeAdder(nCount,sal_Int32(1)) ); + OUString text( OUString::number(nCount) ); + + // pad with leading space + while( text.getLength() < 3 ) + text = " " + text; + + text = "Sprites: " + text; + + renderInfoText( rOutDev, + text, + Point(0, 30) ); + } + + void SpriteCanvasHelper::renderMemUsage( OutputDevice& rOutDev ) + { + BackBufferSharedPtr pBackBuffer( mpOwningSpriteCanvas->getBackBuffer() ); + + if( !(mpRedrawManager && + pBackBuffer) ) + return; + + double nPixel(0.0); + + // accumulate pixel count for each sprite into fCount + mpRedrawManager->forEachSprite( + [&nPixel]( const ::canvas::Sprite::Reference& rSprite ) + { makeAdder( nPixel, 1.0 )( calcNumPixel(rSprite) ); } + ); + + static const int NUM_VIRDEV(2); + static const int BYTES_PER_PIXEL(3); + + const Size& rVDevSize( maVDev->GetOutputSizePixel() ); + const Size& rBackBufferSize( pBackBuffer->getOutDev().GetOutputSizePixel() ); + + const double nMemUsage( nPixel * NUM_VIRDEV * BYTES_PER_PIXEL + + rVDevSize.Width()*rVDevSize.Height() * BYTES_PER_PIXEL + + rBackBufferSize.Width()*rBackBufferSize.Height() * BYTES_PER_PIXEL ); + + OUString text( ::rtl::math::doubleToUString( nMemUsage / 1048576.0, + rtl_math_StringFormat_F, + 2,'.',nullptr,' ') ); + + // pad with leading space + while( text.getLength() < 4 ) + text = " " + text; + + text = "Mem: " + text + "MB"; + + renderInfoText( rOutDev, + text, + Point(0, 60) ); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/vcl/spritecanvashelper.hxx b/canvas/source/vcl/spritecanvashelper.hxx new file mode 100644 index 0000000000..af3f5526a8 --- /dev/null +++ b/canvas/source/vcl/spritecanvashelper.hxx @@ -0,0 +1,169 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/rendering/XAnimatedSprite.hpp> +#include <com/sun/star/rendering/XAnimation.hpp> +#include <com/sun/star/rendering/XCustomSprite.hpp> + +#include <vcl/vclptr.hxx> +#include <vcl/virdev.hxx> + +#include <spriteredrawmanager.hxx> +#include <canvas/elapsedtime.hxx> +#include "canvashelper.hxx" + + +namespace vclcanvas +{ + class SpriteCanvas; + + class SpriteCanvasHelper : public CanvasHelper + { + public: + SpriteCanvasHelper(); + ~SpriteCanvasHelper(); + + void init( const OutDevProviderSharedPtr& rOutDev, + SpriteCanvas& rOwningSpriteCanvas, + ::canvas::SpriteRedrawManager& rManager, + bool bProtect, + bool bHaveAlpha ); + + /// Dispose all internal references + void disposing(); + + // XSpriteCanvas + css::uno::Reference< + css::rendering::XAnimatedSprite > createSpriteFromAnimation( + const css::uno::Reference< css::rendering::XAnimation >& animation ); + + css::uno::Reference< + css::rendering::XAnimatedSprite > createSpriteFromBitmaps( + const css::uno::Sequence< + css::uno::Reference< + css::rendering::XBitmap > >& animationBitmaps, + sal_Int8 interpolationMode ); + + css::uno::Reference< + css::rendering::XCustomSprite > createCustomSprite( + const css::geometry::RealSize2D& spriteSize ); + + css::uno::Reference< + css::rendering::XSprite > createClonedSprite( + const css::uno::Reference< css::rendering::XSprite >& original ); + + /** Actually perform the screen update + + @param bUpdateAll + sal_True, if everything must be updated, not only changed + sprites + + @param io_bSurfaceDirty + In/out parameter, whether backbuffer surface is dirty (if + yes, we're performing a full update, anyway) + */ + bool updateScreen( bool bUpdateAll, + bool& io_bSurfaceDirty ); + + // SpriteRedrawManager functor calls + + + /** Gets called for simple background repaints + */ + void backgroundPaint( const ::basegfx::B2DRange& rUpdateRect ); + + /** Gets called when area can be handled by scrolling. + + Called method must copy screen content from rMoveStart to + rMoveEnd, and restore the background in the uncovered + areas. + + @param rMoveStart + Source rect of the scroll + + @param rMoveEnd + Dest rect of the scroll + + @param rUpdateArea + All info necessary, should rMoveStart be partially or + fully outside the outdev + */ + void scrollUpdate( const ::basegfx::B2DRange& rMoveStart, + const ::basegfx::B2DRange& rMoveEnd, + const ::canvas::SpriteRedrawManager::UpdateArea& rUpdateArea ); + + void opaqueUpdate( const ::basegfx::B2DRange& rTotalArea, + const std::vector< ::canvas::Sprite::Reference >& rSortedUpdateSprites ); + + void genericUpdate( const ::basegfx::B2DRange& rTotalArea, + const std::vector< ::canvas::Sprite::Reference >& rSortedUpdateSprites ); + + css::uno::Any isUnsafeScrolling() const + { + return css::uno::Any(mbIsUnsafeScrolling); + } + void enableUnsafeScrolling( const css::uno::Any& rAny ) + { + mbIsUnsafeScrolling = rAny.get<bool>(); + } + + css::uno::Any isSpriteBounds() const + { + return css::uno::Any(mbShowSpriteBounds); + } + void enableSpriteBounds( const css::uno::Any& rAny ) + { + mbShowSpriteBounds = rAny.get<bool>(); + } + + private: + void renderFrameCounter( OutputDevice& rOutDev ); + void renderSpriteCount( OutputDevice& rOutDev ); + void renderMemUsage( OutputDevice& rOutDev ); + + /// Set from the SpriteCanvas: instance coordinating sprite redraw + ::canvas::SpriteRedrawManager* mpRedrawManager; + + /// Set from the init method. used to generate sprites + SpriteCanvas* mpOwningSpriteCanvas; + + /** Background compositing surface. + + Typically, sprites will be composited in the background, + before pushing them to screen. This happens here. + */ + VclPtr< VirtualDevice > maVDev; + + /// For the frame counter timings + ::canvas::tools::ElapsedTime maLastUpdate; + + /// When true, canvas displays debug info on each frame + bool mbShowFrameInfo; + + /// When true, canvas creates all new sprites with red lines in the corners + bool mbShowSpriteBounds; + + /// When true, canvas uses the scroll optimization (direct scrolls in front buffer) + bool mbIsUnsafeScrolling; + }; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/vcl/spritedevicehelper.cxx b/canvas/source/vcl/spritedevicehelper.cxx new file mode 100644 index 0000000000..99f8b19145 --- /dev/null +++ b/canvas/source/vcl/spritedevicehelper.cxx @@ -0,0 +1,119 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <osl/diagnose.h> +#include <vcl/bitmapex.hxx> +#include <vcl/dibtools.hxx> +#include <tools/stream.hxx> + +#include "spritedevicehelper.hxx" +#include "impltools.hxx" + +using namespace ::com::sun::star; + +namespace vclcanvas +{ + SpriteDeviceHelper::SpriteDeviceHelper() + { + } + + void SpriteDeviceHelper::init( const OutDevProviderSharedPtr& pOutDev ) + { + DeviceHelper::init(pOutDev); + + // setup back buffer + OutputDevice& rOutDev( pOutDev->getOutDev() ); + mpBackBuffer = std::make_shared<BackBuffer>( rOutDev ); + mpBackBuffer->setSize( rOutDev.GetOutputSizePixel() ); + + tools::SetDefaultDeviceAntiAliasing( &mpBackBuffer->getOutDev()); + } + + bool SpriteDeviceHelper::showBuffer( bool, bool ) + { + OSL_FAIL("Not supposed to be called, handled by SpriteCanvas"); + return false; + } + + bool SpriteDeviceHelper::switchBuffer( bool, bool ) + { + OSL_FAIL("Not supposed to be called, handled by SpriteCanvas"); + return false; + } + + void SpriteDeviceHelper::disposing() + { + // release all references + mpBackBuffer.reset(); + + DeviceHelper::disposing(); + } + + uno::Any SpriteDeviceHelper::isAccelerated() const + { + return DeviceHelper::isAccelerated(); + } + + uno::Any SpriteDeviceHelper::getDeviceHandle() const + { + return DeviceHelper::getDeviceHandle(); + } + + uno::Any SpriteDeviceHelper::getSurfaceHandle() const + { + if( !mpBackBuffer ) + return uno::Any(); + + return uno::Any( + reinterpret_cast< sal_Int64 >(&mpBackBuffer->getOutDev()) ); + } + + void SpriteDeviceHelper::notifySizeUpdate( const awt::Rectangle& rBounds ) + { + if( mpBackBuffer ) + mpBackBuffer->setSize( ::Size(rBounds.Width, + rBounds.Height) ); + } + + void SpriteDeviceHelper::dumpScreenContent() const + { + DeviceHelper::dumpScreenContent(); + + static sal_Int32 nFilePostfixCount(0); + + if( mpBackBuffer ) + { + OUString aFilename = "dbg_backbuffer" + OUString::number(nFilePostfixCount) + ".bmp"; + + SvFileStream aStream( aFilename, StreamMode::STD_READWRITE ); + + const ::Point aEmptyPoint; + mpBackBuffer->getOutDev().EnableMapMode( false ); + mpBackBuffer->getOutDev().SetAntialiasing( AntialiasingFlags::Enable ); + WriteDIB(mpBackBuffer->getOutDev().GetBitmapEx(aEmptyPoint, mpBackBuffer->getOutDev().GetOutputSizePixel()), aStream, false); + } + + ++nFilePostfixCount; + } + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/vcl/spritedevicehelper.hxx b/canvas/source/vcl/spritedevicehelper.hxx new file mode 100644 index 0000000000..5f4ef570a6 --- /dev/null +++ b/canvas/source/vcl/spritedevicehelper.hxx @@ -0,0 +1,60 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/awt/Rectangle.hpp> + +#include "backbuffer.hxx" +#include "devicehelper.hxx" + + +/* Definition of DeviceHelper class */ + +namespace vclcanvas +{ + class SpriteDeviceHelper : public DeviceHelper + { + public: + SpriteDeviceHelper(); + + void init( const OutDevProviderSharedPtr& rOutDev ); + + /// Dispose all internal references + void disposing(); + + bool showBuffer( bool bWindowVisible, bool bUpdateAll ); + bool switchBuffer( bool bWindowVisible, bool bUpdateAll ); + + css::uno::Any isAccelerated() const; + css::uno::Any getDeviceHandle() const; + css::uno::Any getSurfaceHandle() const; + + void dumpScreenContent() const; + const BackBufferSharedPtr& getBackBuffer() const { return mpBackBuffer; } + + void notifySizeUpdate( const css::awt::Rectangle& rBounds ); + + private: + /// This buffer holds the background content for all associated canvases + BackBufferSharedPtr mpBackBuffer; + }; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/vcl/spritehelper.cxx b/canvas/source/vcl/spritehelper.cxx new file mode 100644 index 0000000000..f188526930 --- /dev/null +++ b/canvas/source/vcl/spritehelper.cxx @@ -0,0 +1,233 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> +#include <sal/log.hxx> + +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/numeric/ftools.hxx> +#include <basegfx/point/b2dpoint.hxx> +#include <basegfx/range/b2drectangle.hxx> +#include <basegfx/utils/canvastools.hxx> +#include <rtl/math.hxx> +#include <comphelper/diagnose_ex.hxx> +#include <vcl/alpha.hxx> +#include <vcl/bitmapex.hxx> +#include <vcl/canvastools.hxx> +#include <vcl/outdev.hxx> + +#include <canvas/canvastools.hxx> + +#include "spritehelper.hxx" + +using namespace ::com::sun::star; + + +namespace vclcanvas +{ + SpriteHelper::SpriteHelper() : + mbShowSpriteBounds(false) + { + } + + void SpriteHelper::init( const geometry::RealSize2D& rSpriteSize, + const ::canvas::SpriteSurface::Reference& rOwningSpriteCanvas, + const BackBufferSharedPtr& rBackBuffer, + const BackBufferSharedPtr& rBackBufferMask, + bool bShowSpriteBounds ) + { + ENSURE_OR_THROW( rOwningSpriteCanvas && rBackBuffer && rBackBufferMask, + "SpriteHelper::init(): Invalid sprite canvas or back buffer" ); + + mpBackBuffer = rBackBuffer; + mpBackBufferMask = rBackBufferMask; + mbShowSpriteBounds = bShowSpriteBounds; + + init( rSpriteSize, rOwningSpriteCanvas ); + } + + void SpriteHelper::disposing() + { + mpBackBuffer.reset(); + mpBackBufferMask.reset(); + + // forward to parent + CanvasCustomSpriteHelper::disposing(); + } + + void SpriteHelper::redraw( OutputDevice& rTargetSurface, + const ::basegfx::B2DPoint& rPos, + bool& io_bSurfacesDirty, + bool /*bBufferedUpdate*/ ) const + { + if( !mpBackBuffer || + !mpBackBufferMask ) + { + return; // we're disposed + } + + // log output pos in device pixel + SAL_INFO("canvas.vcl", "SpriteHelper::redraw(): output pos is (" << + rPos.getX() << "," << rPos.getY() << ")"); + + const double fAlpha( getAlpha() ); + + if( !isActive() || ::basegfx::fTools::equalZero( fAlpha ) ) + return; + + const ::basegfx::B2DVector& rOrigOutputSize( getSizePixel() ); + + // might get changed below (e.g. adapted for + // transformations). IMPORTANT: both position and size are + // rounded to integer values. From now on, only those + // rounded values are used, to keep clip and content in + // sync. + ::Size aOutputSize( vcl::unotools::sizeFromB2DSize( rOrigOutputSize ) ); + ::Point aOutPos( vcl::unotools::pointFromB2DPoint( rPos ) ); + + + // TODO(F3): Support for alpha-VDev + + // Do we have to update our bitmaps (necessary if virdev + // was painted to, or transformation changed)? + const bool bNeedBitmapUpdate( io_bSurfacesDirty || + hasTransformChanged() || + maContent->IsEmpty() ); + + // updating content of sprite cache - surface is no + // longer dirty in relation to our cache + io_bSurfacesDirty = false; + transformUpdated(); + + if( bNeedBitmapUpdate ) + { + const Point aEmptyPoint; + BitmapEx aBmp( mpBackBuffer->getOutDev().GetBitmapEx( aEmptyPoint, + aOutputSize ) ); + + if( isContentFullyOpaque() ) + { + // optimized case: content canvas is fully + // opaque. Note: since we retrieved aBmp directly + // from an OutDev, it's already a 'display bitmap' + // on windows. + maContent = aBmp; + } + else + { + // sprite content might contain alpha, create + // BmpEx, then. + BitmapEx aMask( mpBackBufferMask->getOutDev().GetBitmapEx( aEmptyPoint, + aOutputSize ) ); + AlphaMask aAlpha( aMask.GetBitmap() ); + aAlpha.Invert(); + + // Note: since we retrieved aBmp and aMask + // directly from an OutDev, it's already a + // 'display bitmap' on windows. + maContent = BitmapEx( aBmp.GetBitmap(), aAlpha ); + } + } + + ::basegfx::B2DHomMatrix aTransform( getTransformation() ); + + rTargetSurface.Push( vcl::PushFlags::CLIPREGION ); + + // apply clip (if any) + if( getClip().is() ) + { + ::basegfx::B2DPolyPolygon aClipPoly( + ::basegfx::unotools::b2DPolyPolygonFromXPolyPolygon2D( + getClip() )); + + if( aClipPoly.count() ) + { + // Move the clip to the final sprite output position. + ::basegfx::B2DHomMatrix aClipTransform( aTransform ); + aClipTransform.translate( aOutPos.X(), aOutPos.Y() ); + aClipPoly.transform( aClipTransform ); + + if( mbShowSpriteBounds ) + { + // Paint green sprite clip area + rTargetSurface.SetLineColor( Color( 0,255,0 ) ); + rTargetSurface.SetFillColor(); + + rTargetSurface.DrawPolyPolygon(::tools::PolyPolygon(aClipPoly)); // #i76339# + } + + vcl::Region aClipRegion( aClipPoly ); + rTargetSurface.SetClipRegion( aClipRegion ); + } + } + + ::basegfx::B2DHomMatrix aSizeTransform, aMoveTransform; + aSizeTransform.scale( aOutputSize.Width(), aOutputSize.Height() ); + aMoveTransform.translate( aOutPos.X(), aOutPos.Y() ); + aTransform = aMoveTransform * aTransform * aSizeTransform; + + rTargetSurface.DrawTransformedBitmapEx( aTransform, *maContent, fAlpha ); + + rTargetSurface.Pop(); + + if( !mbShowSpriteBounds ) + return; + + ::tools::PolyPolygon aMarkerPoly( + ::canvas::tools::getBoundMarksPolyPolygon( + ::basegfx::B2DRectangle(aOutPos.X(), + aOutPos.Y(), + aOutPos.X() + aOutputSize.Width()-1, + aOutPos.Y() + aOutputSize.Height()-1) ) ); + + // Paint little red sprite area markers + rTargetSurface.SetLineColor( COL_RED ); + rTargetSurface.SetFillColor(); + + for( int i=0; i<aMarkerPoly.Count(); ++i ) + { + rTargetSurface.DrawPolyLine( aMarkerPoly.GetObject(static_cast<sal_uInt16>(i)) ); + } + + // paint sprite prio + vcl::Font aVCLFont; + aVCLFont.SetFontHeight( std::min(::tools::Long(20),aOutputSize.Height()) ); + aVCLFont.SetColor( COL_RED ); + + rTargetSurface.SetTextAlign(ALIGN_TOP); + rTargetSurface.SetTextColor( COL_RED ); + rTargetSurface.SetFont( aVCLFont ); + + OUString text( ::rtl::math::doubleToUString( getPriority(), + rtl_math_StringFormat_F, + 2,'.',nullptr,' ') ); + + rTargetSurface.DrawText( aOutPos+Point(2,2), text ); + SAL_INFO( "canvas.vcl", + "sprite " << this << " has prio " << getPriority()); + } + + ::basegfx::B2DPolyPolygon SpriteHelper::polyPolygonFromXPolyPolygon2D( uno::Reference< rendering::XPolyPolygon2D >& xPoly ) const + { + return ::basegfx::unotools::b2DPolyPolygonFromXPolyPolygon2D( xPoly ); + } + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/vcl/spritehelper.hxx b/canvas/source/vcl/spritehelper.hxx new file mode 100644 index 0000000000..41d9883f6f --- /dev/null +++ b/canvas/source/vcl/spritehelper.hxx @@ -0,0 +1,108 @@ +/* -*- 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 . + */ + +#pragma once + +#include <vcl/bitmapex.hxx> + +#include <base/canvascustomspritehelper.hxx> +#include <base/spritesurface.hxx> +#include <vclwrapper.hxx> + +#include "backbuffer.hxx" + + +namespace vclcanvas +{ + /* Definition of SpriteHelper class */ + + /** Helper class for canvas sprites. + + This class implements all sprite-related functionality, like + that available on the XSprite interface. + */ + class SpriteHelper : public ::canvas::CanvasCustomSpriteHelper + { + public: + SpriteHelper(); + + // make CanvasCustomSpriteHelper::init visible for name lookup + using ::canvas::CanvasCustomSpriteHelper::init; + + /** Late-init the sprite helper + + @param rSpriteSize + Size of the sprite + + @param rSpriteCanvas + Sprite canvas this sprite is part of. Object stores + ref-counted reference to it, thus, don't forget to pass on + disposing()! + + @param rBackBuffer + Buffer of the sprite content (non-alpha part) + + @param rBackBufferMask + Buffer of the sprite content (alpha part) + */ + void init( const css::geometry::RealSize2D& rSpriteSize, + const ::canvas::SpriteSurface::Reference& rOwningSpriteCanvas, + const BackBufferSharedPtr& rBackBuffer, + const BackBufferSharedPtr& rBackBufferMask, + bool bShowSpriteBounds ); + + void disposing(); + + /** Repaint sprite content to associated sprite canvas + + @param rPos + Output position (sprite's own position is disregarded) + + @param io_bSurfacesDirty + When true, the referenced sprite surfaces (backBuffer and + backBufferMask) have been modified since last call. + + @param bBufferedUpdate + When true, the redraw does <em>not</em> happen directly on + the front buffer, but within a VDev. Used to speed up + drawing. + */ + void redraw( OutputDevice& rOutDev, + const ::basegfx::B2DPoint& rPos, + bool& bSurfacesDirty, + bool bBufferedUpdate ) const; + + private: + virtual ::basegfx::B2DPolyPolygon polyPolygonFromXPolyPolygon2D( + css::uno::Reference< css::rendering::XPolyPolygon2D >& xPoly ) const override; + + // for the redraw + BackBufferSharedPtr mpBackBuffer; + BackBufferSharedPtr mpBackBufferMask; + + /// Cached bitmap for the current sprite content + mutable ::canvas::vcltools::VCLObject<BitmapEx> maContent; + + /// When true, line sprite corners in red + bool mbShowSpriteBounds; + + }; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/vcl/textlayout.cxx b/canvas/source/vcl/textlayout.cxx new file mode 100644 index 0000000000..25318ee1fb --- /dev/null +++ b/canvas/source/vcl/textlayout.cxx @@ -0,0 +1,451 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + + +#include <sal/config.h> + +#include <comphelper/diagnose_ex.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/numeric/ftools.hxx> +#include <basegfx/utils/canvastools.hxx> +#include <com/sun/star/rendering/CompositeOperation.hpp> +#include <com/sun/star/rendering/RenderState.hpp> +#include <com/sun/star/rendering/TextDirection.hpp> +#include <com/sun/star/rendering/ViewState.hpp> +#include <comphelper/sequence.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <utility> +#include <vcl/kernarray.hxx> +#include <vcl/metric.hxx> +#include <vcl/virdev.hxx> + +#include <canvas/canvastools.hxx> + +#include "textlayout.hxx" + +#include <memory> + +using namespace ::com::sun::star; + +namespace vclcanvas +{ + namespace + { + void setupLayoutMode( OutputDevice& rOutDev, + sal_Int8 nTextDirection ) + { + // TODO(P3): avoid if already correctly set + vcl::text::ComplexTextLayoutFlags nLayoutMode = vcl::text::ComplexTextLayoutFlags::Default; + switch( nTextDirection ) + { + case rendering::TextDirection::WEAK_LEFT_TO_RIGHT: + break; + case rendering::TextDirection::STRONG_LEFT_TO_RIGHT: + nLayoutMode = vcl::text::ComplexTextLayoutFlags::BiDiStrong; + break; + case rendering::TextDirection::WEAK_RIGHT_TO_LEFT: + nLayoutMode = vcl::text::ComplexTextLayoutFlags::BiDiRtl; + break; + case rendering::TextDirection::STRONG_RIGHT_TO_LEFT: + nLayoutMode = vcl::text::ComplexTextLayoutFlags::BiDiRtl | vcl::text::ComplexTextLayoutFlags::BiDiStrong; + break; + default: + break; + } + + // set calculated layout mode. Origin is always the left edge, + // as required at the API spec + rOutDev.SetLayoutMode( nLayoutMode | vcl::text::ComplexTextLayoutFlags::TextOriginLeft ); + } + } + + TextLayout::TextLayout( rendering::StringContext aText, + sal_Int8 nDirection, + CanvasFont::Reference rFont, + uno::Reference<rendering::XGraphicDevice> xDevice, + OutDevProviderSharedPtr xOutDev ) : + maText(std::move( aText )), + mpFont(std::move( rFont )), + mxDevice(std::move( xDevice )), + mpOutDevProvider(std::move( xOutDev )), + mnTextDirection( nDirection ) + {} + + void TextLayout::disposing(std::unique_lock<std::mutex>& rGuard) + { + rGuard.unlock(); + { + SolarMutexGuard aGuard; + mpOutDevProvider.reset(); + mxDevice.clear(); + mpFont.clear(); + } + rGuard.lock(); + } + + // XTextLayout + uno::Sequence< uno::Reference< rendering::XPolyPolygon2D > > SAL_CALL TextLayout::queryTextShapes( ) + { + SolarMutexGuard aGuard; + + OutputDevice& rOutDev = mpOutDevProvider->getOutDev(); + ScopedVclPtrInstance< VirtualDevice > pVDev( rOutDev ); + pVDev->SetFont( mpFont->getVCLFont() ); + + setupLayoutMode( *pVDev, mnTextDirection ); + + const rendering::ViewState aViewState( + geometry::AffineMatrix2D(1,0,0, 0,1,0), + nullptr); + + rendering::RenderState aRenderState ( + geometry::AffineMatrix2D(1,0,0,0,1,0), + nullptr, + uno::Sequence<double>(4), + rendering::CompositeOperation::SOURCE); + + KernArray aOffsets(setupTextOffsets(maLogicalAdvancements, aViewState, aRenderState)); + std::span<const sal_Bool> aKashidaArray(maKashidaPositions.getArray(), maKashidaPositions.getLength()); + + std::vector< uno::Reference< rendering::XPolyPolygon2D> > aOutlineSequence; + ::basegfx::B2DPolyPolygonVector aOutlines; + if (pVDev->GetTextOutlines( + aOutlines, + maText.Text, + maText.StartPosition, + maText.StartPosition, + maText.Length, + 0, + aOffsets, + aKashidaArray)) + { + aOutlineSequence.reserve(aOutlines.size()); + sal_Int32 nIndex (0); + for (auto const& outline : aOutlines) + { + aOutlineSequence[nIndex++] = ::basegfx::unotools::xPolyPolygonFromB2DPolyPolygon( + mxDevice, + outline); + } + } + + return comphelper::containerToSequence(aOutlineSequence); + } + + uno::Sequence< geometry::RealRectangle2D > SAL_CALL TextLayout::queryInkMeasures( ) + { + SolarMutexGuard aGuard; + + + OutputDevice& rOutDev = mpOutDevProvider->getOutDev(); + ScopedVclPtrInstance< VirtualDevice > pVDev( rOutDev ); + pVDev->SetFont( mpFont->getVCLFont() ); + + setupLayoutMode( *pVDev, mnTextDirection ); + + const rendering::ViewState aViewState( + geometry::AffineMatrix2D(1,0,0, 0,1,0), + nullptr); + + rendering::RenderState aRenderState ( + geometry::AffineMatrix2D(1,0,0,0,1,0), + nullptr, + uno::Sequence<double>(4), + rendering::CompositeOperation::SOURCE); + + KernArray aOffsets(setupTextOffsets(maLogicalAdvancements, aViewState, aRenderState)); + + std::vector< ::tools::Rectangle > aMetricVector; + uno::Sequence<geometry::RealRectangle2D> aBoundingBoxes; + if (pVDev->GetGlyphBoundRects( + Point(0,0), + maText.Text, + ::canvas::tools::numeric_cast<sal_uInt16>(maText.StartPosition), + ::canvas::tools::numeric_cast<sal_uInt16>(maText.Length), + aMetricVector)) + { + aBoundingBoxes.realloc(aMetricVector.size()); + auto pBoundingBoxes = aBoundingBoxes.getArray(); + sal_Int32 nIndex (0); + for (auto const& metric : aMetricVector) + { + pBoundingBoxes[nIndex++] = geometry::RealRectangle2D( + metric.Left(), + metric.Top(), + metric.Right(), + metric.Bottom()); + } + } + return aBoundingBoxes; + } + + uno::Sequence< geometry::RealRectangle2D > SAL_CALL TextLayout::queryMeasures( ) + { + // TODO(F1) + return uno::Sequence< geometry::RealRectangle2D >(); + } + + uno::Sequence< double > SAL_CALL TextLayout::queryLogicalAdvancements( ) + { + SolarMutexGuard aGuard; + + return maLogicalAdvancements; + } + + void SAL_CALL TextLayout::applyLogicalAdvancements( const uno::Sequence< double >& aAdvancements ) + { + SolarMutexGuard aGuard; + + ENSURE_ARG_OR_THROW( aAdvancements.getLength() == maText.Length, + "TextLayout::applyLogicalAdvancements(): mismatching number of advancements" ); + + maLogicalAdvancements = aAdvancements; + } + + uno::Sequence< sal_Bool > SAL_CALL TextLayout::queryKashidaPositions( ) + { + SolarMutexGuard aGuard; + + return maKashidaPositions; + } + + void SAL_CALL TextLayout::applyKashidaPositions( const uno::Sequence< sal_Bool >& aPositions ) + { + SolarMutexGuard aGuard; + + ENSURE_ARG_OR_THROW( !aPositions.hasElements() || aPositions.getLength() == maText.Length, + "TextLayout::applyKashidaPositions(): mismatching number of positions" ); + + maKashidaPositions = aPositions; + } + + geometry::RealRectangle2D SAL_CALL TextLayout::queryTextBounds( ) + { + SolarMutexGuard aGuard; + + if( !mpOutDevProvider ) + return geometry::RealRectangle2D(); + + OutputDevice& rOutDev = mpOutDevProvider->getOutDev(); + + ScopedVclPtrInstance< VirtualDevice > pVDev( rOutDev ); + pVDev->SetFont( mpFont->getVCLFont() ); + + // need metrics for Y offset, the XCanvas always renders + // relative to baseline + const ::FontMetric& aMetric( pVDev->GetFontMetric() ); + + setupLayoutMode( *pVDev, mnTextDirection ); + + const sal_Int32 nAboveBaseline( -aMetric.GetAscent() ); + const sal_Int32 nBelowBaseline( aMetric.GetDescent() ); + + if( maLogicalAdvancements.hasElements() ) + { + return geometry::RealRectangle2D( 0, nAboveBaseline, + maLogicalAdvancements[ maLogicalAdvancements.getLength()-1 ], + nBelowBaseline ); + } + else + { + return geometry::RealRectangle2D( 0, nAboveBaseline, + pVDev->GetTextWidth( + maText.Text, + ::canvas::tools::numeric_cast<sal_uInt16>(maText.StartPosition), + ::canvas::tools::numeric_cast<sal_uInt16>(maText.Length) ), + nBelowBaseline ); + } + } + + double SAL_CALL TextLayout::justify( double ) + { + // TODO(F1) + return 0.0; + } + + double SAL_CALL TextLayout::combinedJustify( const uno::Sequence< uno::Reference< rendering::XTextLayout > >&, + double ) + { + // TODO(F1) + return 0.0; + } + + rendering::TextHit SAL_CALL TextLayout::getTextHit( const geometry::RealPoint2D& ) + { + // TODO(F1) + return rendering::TextHit(); + } + + rendering::Caret SAL_CALL TextLayout::getCaret( sal_Int32, sal_Bool ) + { + // TODO(F1) + return rendering::Caret(); + } + + sal_Int32 SAL_CALL TextLayout::getNextInsertionIndex( sal_Int32, sal_Int32, sal_Bool ) + { + // TODO(F1) + return 0; + } + + uno::Reference< rendering::XPolyPolygon2D > SAL_CALL TextLayout::queryVisualHighlighting( sal_Int32, sal_Int32 ) + { + // TODO(F1) + return uno::Reference< rendering::XPolyPolygon2D >(); + } + + uno::Reference< rendering::XPolyPolygon2D > SAL_CALL TextLayout::queryLogicalHighlighting( sal_Int32, sal_Int32 ) + { + // TODO(F1) + return uno::Reference< rendering::XPolyPolygon2D >(); + } + + double SAL_CALL TextLayout::getBaselineOffset( ) + { + // TODO(F1) + return 0.0; + } + + sal_Int8 SAL_CALL TextLayout::getMainTextDirection( ) + { + return mnTextDirection; + } + + uno::Reference< rendering::XCanvasFont > SAL_CALL TextLayout::getFont( ) + { + SolarMutexGuard aGuard; + + return mpFont; + } + + rendering::StringContext SAL_CALL TextLayout::getText( ) + { + return maText; + } + + void TextLayout::draw( OutputDevice& rOutDev, + const Point& rOutpos, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState ) const + { + SolarMutexGuard aGuard; + +#ifdef _WIN32 + // tdf#147999 + // On Windows we get the wrong font width for fallback fonts unless we setup again here. + vcl::Font aFont(rOutDev.GetFont()); + tools::setupFontWidth(mpFont->getFontMatrix(), aFont, rOutDev); + rOutDev.SetFont(aFont); +#endif + + setupLayoutMode( rOutDev, mnTextDirection ); + + if( maLogicalAdvancements.hasElements() ) + { + // TODO(P2): cache that + KernArray aOffsets(setupTextOffsets(maLogicalAdvancements, viewState, renderState)); + std::span<const sal_Bool> aKashidaArray(maKashidaPositions.getConstArray(), maKashidaPositions.getLength()); + + // TODO(F3): ensure correct length and termination for DX + // array (last entry _must_ contain the overall width) + + rOutDev.DrawTextArray( rOutpos, + maText.Text, + aOffsets, + aKashidaArray, + ::canvas::tools::numeric_cast<sal_uInt16>(maText.StartPosition), + ::canvas::tools::numeric_cast<sal_uInt16>(maText.Length) ); + } + else + { + rOutDev.DrawText( rOutpos, + maText.Text, + ::canvas::tools::numeric_cast<sal_uInt16>(maText.StartPosition), + ::canvas::tools::numeric_cast<sal_uInt16>(maText.Length) ); + } + } + + namespace + { + class OffsetTransformer + { + public: + explicit OffsetTransformer( ::basegfx::B2DHomMatrix aMat ) : + maMatrix(std::move( aMat )) + { + } + + sal_Int32 operator()( const double& rOffset ) + { + // This is an optimization of the normal rMat*[x,0] + // transformation of the advancement vector (in x + // direction), followed by a length calculation of the + // resulting vector: advancement' = + // ||rMat*[x,0]||. Since advancements are vectors, we + // can ignore translational components, thus if [x,0], + // it follows that rMat*[x,0]=[x',0] holds. Thus, we + // just have to calc the transformation of the x + // component. + + // TODO(F2): Handle non-horizontal advancements! + return ::basegfx::fround( hypot(maMatrix.get(0,0)*rOffset, + maMatrix.get(1,0)*rOffset) ); + } + + private: + ::basegfx::B2DHomMatrix maMatrix; + }; + } + + KernArray TextLayout::setupTextOffsets( + const uno::Sequence< double >& inputOffsets, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState ) const + { + ::basegfx::B2DHomMatrix aMatrix; + + ::canvas::tools::mergeViewAndRenderTransform(aMatrix, + viewState, + renderState); + + // fill integer offsets + KernArray outputOffsets; + OffsetTransformer aTransform(aMatrix); + std::for_each(inputOffsets.begin(), inputOffsets.end(), + [&outputOffsets, &aTransform](double n) {outputOffsets.push_back(aTransform(n)); } ); + return outputOffsets; + } + + OUString SAL_CALL TextLayout::getImplementationName() + { + return "VCLCanvas::TextLayout"; + } + + sal_Bool SAL_CALL TextLayout::supportsService( const OUString& ServiceName ) + { + return cppu::supportsService( this, ServiceName ); + } + + uno::Sequence< OUString > SAL_CALL TextLayout::getSupportedServiceNames() + { + return { "com.sun.star.rendering.TextLayout" }; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/vcl/textlayout.hxx b/canvas/source/vcl/textlayout.hxx new file mode 100644 index 0000000000..998383cb02 --- /dev/null +++ b/canvas/source/vcl/textlayout.hxx @@ -0,0 +1,103 @@ +/* -*- 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 . + */ + +#pragma once + +#include <comphelper/compbase.hxx> + +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/rendering/StringContext.hpp> +#include <com/sun/star/rendering/XTextLayout.hpp> + +#include "canvasfont.hxx" +#include "impltools.hxx" + +/* Definition of TextLayout class */ + +namespace vclcanvas +{ + typedef ::comphelper::WeakComponentImplHelper< css::rendering::XTextLayout, + css::lang::XServiceInfo > TextLayout_Base; + + class TextLayout : public TextLayout_Base + { + public: + /// make noncopyable + TextLayout(const TextLayout&) = delete; + const TextLayout& operator=(const TextLayout&) = delete; + + TextLayout( css::rendering::StringContext aText, + sal_Int8 nDirection, + CanvasFont::Reference rFont, + css::uno::Reference< + css::rendering::XGraphicDevice> xDevice, + OutDevProviderSharedPtr xOutDev ); + + /// Dispose all internal references + virtual void disposing(std::unique_lock<std::mutex>& rGuard) override; + + // XTextLayout + virtual css::uno::Sequence< css::uno::Reference< css::rendering::XPolyPolygon2D > > SAL_CALL queryTextShapes( ) override; + virtual css::uno::Sequence< css::geometry::RealRectangle2D > SAL_CALL queryInkMeasures( ) override; + virtual css::uno::Sequence< css::geometry::RealRectangle2D > SAL_CALL queryMeasures( ) override; + virtual css::uno::Sequence< double > SAL_CALL queryLogicalAdvancements( ) override; + virtual void SAL_CALL applyLogicalAdvancements( const css::uno::Sequence< double >& aAdvancements ) override; + virtual css::uno::Sequence< sal_Bool > SAL_CALL queryKashidaPositions( ) override; + virtual void SAL_CALL applyKashidaPositions( const css::uno::Sequence< sal_Bool >& aPositions ) override; + virtual css::geometry::RealRectangle2D SAL_CALL queryTextBounds( ) override; + virtual double SAL_CALL justify( double nSize ) override; + virtual double SAL_CALL combinedJustify( const css::uno::Sequence< css::uno::Reference< css::rendering::XTextLayout > >& aNextLayouts, double nSize ) override; + virtual css::rendering::TextHit SAL_CALL getTextHit( const css::geometry::RealPoint2D& aHitPoint ) override; + virtual css::rendering::Caret SAL_CALL getCaret( sal_Int32 nInsertionIndex, sal_Bool bExcludeLigatures ) override; + virtual sal_Int32 SAL_CALL getNextInsertionIndex( sal_Int32 nStartIndex, sal_Int32 nCaretAdvancement, sal_Bool bExcludeLigatures ) override; + virtual css::uno::Reference< css::rendering::XPolyPolygon2D > SAL_CALL queryVisualHighlighting( sal_Int32 nStartIndex, sal_Int32 nEndIndex ) override; + virtual css::uno::Reference< css::rendering::XPolyPolygon2D > SAL_CALL queryLogicalHighlighting( sal_Int32 nStartIndex, sal_Int32 nEndIndex ) override; + virtual double SAL_CALL getBaselineOffset( ) override; + virtual sal_Int8 SAL_CALL getMainTextDirection( ) override; + virtual css::uno::Reference< css::rendering::XCanvasFont > SAL_CALL getFont( ) override; + virtual css::rendering::StringContext SAL_CALL getText( ) override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override; + + void draw( OutputDevice& rOutDev, + const Point& rOutpos, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState ) const; + + private: + KernArray setupTextOffsets( + const css::uno::Sequence< double >& inputOffsets, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState ) const; + + css::rendering::StringContext maText; + css::uno::Sequence< double > maLogicalAdvancements; + css::uno::Sequence< sal_Bool > maKashidaPositions; + CanvasFont::Reference mpFont; + css::uno::Reference< css::rendering::XGraphicDevice> mxDevice; + OutDevProviderSharedPtr mpOutDevProvider; + sal_Int8 mnTextDirection; + }; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/vcl/vclcanvas.component b/canvas/source/vcl/vclcanvas.component new file mode 100644 index 0000000000..c2e0ffc097 --- /dev/null +++ b/canvas/source/vcl/vclcanvas.component @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * 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 . + --> + +<component loader="com.sun.star.loader.SharedLibrary" environment="@CPPU_ENV@" + xmlns="http://openoffice.org/2010/uno-components"> + <implementation name="com.sun.star.comp.rendering.Canvas.VCL" + constructor="com_sun_star_comp_rendering_Canvas_VCL_get_implementation"> + <service name="com.sun.star.rendering.Canvas.VCL"/> + </implementation> + <implementation name="com.sun.star.comp.rendering.SpriteCanvas.VCL" + constructor="com_sun_star_comp_rendering_SpriteCanvas_VCL_get_implementation"> + <service name="com.sun.star.rendering.SpriteCanvas.VCL"/> + </implementation> +</component> diff --git a/canvas/source/vcl/windowoutdevholder.cxx b/canvas/source/vcl/windowoutdevholder.cxx new file mode 100644 index 0000000000..4776ee5548 --- /dev/null +++ b/canvas/source/vcl/windowoutdevholder.cxx @@ -0,0 +1,49 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <com/sun/star/lang/NoSupportException.hpp> +#include <toolkit/helper/vclunohelper.hxx> + +#include "windowoutdevholder.hxx" + +using namespace ::com::sun::star; + +namespace vclcanvas +{ + namespace + { + vcl::Window& windowFromXWin( const uno::Reference<awt::XWindow>& xWin ) + { + VclPtr<vcl::Window> pWindow = VCLUnoHelper::GetWindow(xWin); + if( !pWindow ) + throw lang::NoSupportException( + "Parent window not VCL window, or canvas out-of-process!", + nullptr); + return *pWindow; + } + } + + WindowOutDevHolder::WindowOutDevHolder( const uno::Reference<awt::XWindow>& xWin ) : + mrOutputWindow( windowFromXWin(xWin) ) + {} +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/vcl/windowoutdevholder.hxx b/canvas/source/vcl/windowoutdevholder.hxx new file mode 100644 index 0000000000..87138a8a91 --- /dev/null +++ b/canvas/source/vcl/windowoutdevholder.hxx @@ -0,0 +1,56 @@ +/* -*- 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 . + */ + +#pragma once + +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/awt/XWindow.hpp> +#include <vcl/window.hxx> + +#include "outdevprovider.hxx" + +namespace vclcanvas +{ + class WindowOutDevHolder : public OutDevProvider + { + public: + WindowOutDevHolder(const WindowOutDevHolder&) = delete; + const WindowOutDevHolder operator=(const WindowOutDevHolder&) = delete; + + explicit WindowOutDevHolder( const css::uno::Reference< css::awt::XWindow>& xWin ); + + private: + virtual OutputDevice& getOutDev() override { return *mrOutputWindow.GetOutDev(); } + virtual const OutputDevice& getOutDev() const override { return *mrOutputWindow.GetOutDev(); } + + // TODO(Q2): Lifetime issue. Though WindowGraphicDeviceBase + // now listens to the window component, I still consider + // holding a naked reference unsafe here (especially as we + // pass it around via getOutDev). This _only_ works reliably, + // if disposing the SpriteCanvas correctly disposes all + // entities which hold this pointer. + // So: as soon as the protocol inside + // vcl/source/window/window.cxx is broken, that disposes the + // canvas during window deletion, we're riding a dead horse + // here + vcl::Window& mrOutputWindow; + }; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |