diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 05:54:39 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 05:54:39 +0000 |
commit | 267c6f2ac71f92999e969232431ba04678e7437e (patch) | |
tree | 358c9467650e1d0a1d7227a21dac2e3d08b622b2 /canvas/source/vcl | |
parent | Initial commit. (diff) | |
download | libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.tar.xz libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.zip |
Adding upstream version 4:24.2.0.upstream/4%24.2.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'canvas/source/vcl')
40 files changed, 7756 insertions, 0 deletions
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: */ |