diff options
Diffstat (limited to 'canvas/source/directx')
52 files changed, 10185 insertions, 0 deletions
diff --git a/canvas/source/directx/directx9canvas.component b/canvas/source/directx/directx9canvas.component new file mode 100644 index 000000000..ac75940d2 --- /dev/null +++ b/canvas/source/directx/directx9canvas.component @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + --> + +<component loader="com.sun.star.loader.SharedLibrary" environment="@CPPU_ENV@" + xmlns="http://openoffice.org/2010/uno-components"> + <implementation name="com.sun.star.comp.rendering.SpriteCanvas.DX9" + constructor="canvas_directx9_SpriteCanvas_get_implementation"> + <service name="com.sun.star.rendering.SpriteCanvas.DX9"/> + </implementation> +</component> diff --git a/canvas/source/directx/dx_9rm.cxx b/canvas/source/directx/dx_9rm.cxx new file mode 100644 index 000000000..98c4c14b9 --- /dev/null +++ b/canvas/source/directx/dx_9rm.cxx @@ -0,0 +1,1213 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> +#include <sal/log.hxx> + +#include <memory> +#include <string.h> + +#include <basegfx/numeric/ftools.hxx> +#include <basegfx/point/b2ipoint.hxx> +#include <basegfx/range/b2irectangle.hxx> +#include <basegfx/vector/b2dsize.hxx> +#include <basegfx/vector/b2isize.hxx> +#include <com/sun/star/lang/NoSupportException.hpp> +#include <osl/thread.hxx> +#include <osl/time.h> +#include <tools/diagnose_ex.h> +#include <vcl/syschild.hxx> +#include <vcl/sysdata.hxx> +#include <vcl/window.hxx> + +#include <canvas/elapsedtime.hxx> +#include <canvas/canvastools.hxx> +#include <rendering/icolorbuffer.hxx> +#include <rendering/irendermodule.hxx> +#include <rendering/isurface.hxx> + +#include "dx_config.hxx" +#include "dx_impltools.hxx" +#include "dx_rendermodule.hxx" + +#define MIN_TEXTURE_SIZE (32) +//#define FAKE_MAX_NUMBER_TEXTURES (2) +//#define FAKE_MAX_TEXTURE_SIZE (4096) + +#define VERTEX_BUFFER_SIZE (341*3) // 1023, the size of the internal + // vertex buffer (must be divisible + // by 3, as each triangle primitive + // has 3 vertices) + + +using namespace ::com::sun::star; + + +// 'dxcanvas' namespace + + +namespace dxcanvas +{ + namespace + { + class DXRenderModule; + + + // DXSurface + + + /** ISurface implementation. + + @attention holds the DXRenderModule via non-refcounted + reference! This is safe with current state of affairs, since + the canvas::PageManager holds surface and render module via + shared_ptr (and makes sure all surfaces are deleted before its + render module member goes out of scope). + */ + class DXSurface : public canvas::ISurface + { + public: + DXSurface( DXRenderModule& rRenderModule, + const ::basegfx::B2ISize& rSize ); + ~DXSurface() override; + + virtual bool selectTexture() override; + virtual bool isValid() override; + virtual bool update( const ::basegfx::B2IPoint& rDestPos, + const ::basegfx::B2IRange& rSourceRect, + ::canvas::IColorBuffer& rSource ) override; + virtual ::basegfx::B2IVector getSize(); + + private: + /// Guard local methods against concurrent access to RenderModule + class ImplRenderModuleGuard + { + public: + /// make noncopyable + ImplRenderModuleGuard(const ImplRenderModuleGuard&) = delete; + const ImplRenderModuleGuard& operator=(const ImplRenderModuleGuard&) = delete; + + explicit ImplRenderModuleGuard( DXRenderModule& rRenderModule ); + ~ImplRenderModuleGuard(); + + private: + DXRenderModule& mrRenderModule; + }; + + DXRenderModule& mrRenderModule; + sal::systools::COMReference<IDirect3DTexture9> mpTexture; + + ::basegfx::B2IVector maSize; + }; + + + // DXRenderModule + + + /// Default implementation of IDXRenderModule + class DXRenderModule final: public IDXRenderModule + { + public: + explicit DXRenderModule( const vcl::Window& rWindow ); + ~DXRenderModule() override; + + virtual void lock() const override { maMutex.acquire(); } + virtual void unlock() const override { maMutex.release(); } + + virtual sal::systools::COMReference<IDirect3DSurface9> + createSystemMemorySurface( const ::basegfx::B2IVector& rSize ) override; + virtual void disposing() override; + virtual HWND getHWND() const override { return mhWnd; } + virtual void screenShot() override; + + virtual bool flip( const ::basegfx::B2IRectangle& rUpdateArea, + const ::basegfx::B2IRectangle& rCurrWindowArea ) override; + + virtual void resize( const ::basegfx::B2IRange& rect ) override; + virtual ::basegfx::B2IVector getPageSize() override; + virtual std::shared_ptr<canvas::ISurface> createSurface( const ::basegfx::B2IVector& surfaceSize ) override; + virtual void beginPrimitive( PrimitiveType eType ) override; + virtual void endPrimitive() override; + virtual void pushVertex( const ::canvas::Vertex& vertex ) override; + virtual bool isError() override; + + sal::systools::COMReference<IDirect3DDevice9> getDevice() { return mpDevice; } + + void flushVertexCache(); + void commitVertexCache(); + + private: + + bool create( const vcl::Window& rWindow ); + bool createDevice(); + bool verifyDevice( const UINT nAdapter ); + UINT getAdapterFromWindow(); + + /** This object represents the DirectX state machine. In order + to serialize access to DirectX's global state, a global + mutex is required. + */ + static ::osl::Mutex maMutex; + + HWND mhWnd; + sal::systools::COMReference<IDirect3DDevice9> mpDevice; + sal::systools::COMReference<IDirect3D9> mpDirect3D9; + sal::systools::COMReference<IDirect3DSwapChain9> mpSwapChain; + sal::systools::COMReference<IDirect3DVertexBuffer9> mpVertexBuffer; + std::shared_ptr<canvas::ISurface> mpTexture; + VclPtr<SystemChildWindow> mpWindow; + ::basegfx::B2IVector maSize; + typedef std::vector<canvas::Vertex> vertexCache_t; + vertexCache_t maVertexCache; + std::size_t mnCount; + int mnBeginSceneCount; + bool mbCanUseDynamicTextures; + bool mbError; + PrimitiveType meType; + ::basegfx::B2IVector maPageSize; + D3DPRESENT_PARAMETERS mad3dpp; + + bool isDisposed() const { return (mhWnd==nullptr); } + + struct dxvertex + { + float x,y,z,rhw; + DWORD diffuse; + float u,v; + }; + + std::size_t maNumVertices; + std::size_t maWriteIndex; + std::size_t maReadIndex; + }; + + ::osl::Mutex DXRenderModule::maMutex; + + + // DXSurface::ImplRenderModuleGuard + + + DXSurface::ImplRenderModuleGuard::ImplRenderModuleGuard( + DXRenderModule& rRenderModule ) : + mrRenderModule( rRenderModule ) + { + mrRenderModule.lock(); + } + + DXSurface::ImplRenderModuleGuard::~ImplRenderModuleGuard() + { + mrRenderModule.unlock(); + } + +#ifdef FAKE_MAX_NUMBER_TEXTURES + static sal_uInt32 gNumSurfaces = 0; +#endif + + // DXSurface::DXSurface + + + DXSurface::DXSurface( DXRenderModule& rRenderModule, + const ::basegfx::B2ISize& rSize ) : + mrRenderModule(rRenderModule), + mpTexture(nullptr), + maSize() + { + ImplRenderModuleGuard aGuard( mrRenderModule ); + +#ifdef FAKE_MAX_NUMBER_TEXTURES + ++gNumSurfaces; + if(gNumSurfaces >= FAKE_MAX_NUMBER_TEXTURES) + return; +#endif + +#ifdef FAKE_MAX_TEXTURE_SIZE + if(rSize.getX() > FAKE_MAX_TEXTURE_SIZE) + return; + if(rSize.getY() > FAKE_MAX_TEXTURE_SIZE) + return; +#endif + + ENSURE_ARG_OR_THROW(rSize.getX() > 0 && rSize.getY() > 0, + "DXSurface::DXSurface(): request for zero-sized surface"); + + sal::systools::COMReference<IDirect3DDevice9> pDevice(rRenderModule.getDevice()); + + IDirect3DTexture9 *pTexture(nullptr); + if(FAILED(pDevice->CreateTexture( + rSize.getX(), + rSize.getY(), + 1,0,D3DFMT_A8R8G8B8, + D3DPOOL_MANAGED, + &pTexture,nullptr))) + return; + + mpTexture = sal::systools::COMReference<IDirect3DTexture9>(pTexture, false); + maSize = rSize; + } + + + // DXSurface::~DXSurface + + + DXSurface::~DXSurface() + { + ImplRenderModuleGuard aGuard( mrRenderModule ); + +#ifdef FAKE_MAX_NUMBER_TEXTURES + gNumSurfaces--; +#endif + } + + + // DXSurface::selectTexture + + + bool DXSurface::selectTexture() + { + ImplRenderModuleGuard aGuard( mrRenderModule ); + mrRenderModule.flushVertexCache(); + sal::systools::COMReference<IDirect3DDevice9> pDevice(mrRenderModule.getDevice()); + + if( FAILED(pDevice->SetTexture(0,mpTexture.get())) ) + return false; + + return true; + } + + + // DXSurface::isValid + + + bool DXSurface::isValid() + { + ImplRenderModuleGuard aGuard( mrRenderModule ); + + if(!(mpTexture.is())) + return false; + return true; + } + + + // DXSurface::update + + + bool DXSurface::update( const ::basegfx::B2IPoint& rDestPos, + const ::basegfx::B2IRange& rSourceRect, + ::canvas::IColorBuffer& rSource ) + { + ImplRenderModuleGuard aGuard( mrRenderModule ); + + // can't update if surface is not valid, that means + // either not existent nor restored... + if(!(isValid())) + return false; + + D3DLOCKED_RECT aLockedRect; + RECT rect; + rect.left = std::max(sal_Int32(0),rDestPos.getX()); + rect.top = std::max(sal_Int32(0),rDestPos.getY()); + // to avoid interpolation artifacts from other textures, + // the surface manager allocates one pixel gap between + // them. Clear that to transparent. + rect.right = std::min(maSize.getX(), + rect.left + sal_Int32(rSourceRect.getWidth()+1)); + rect.bottom = std::min(maSize.getY(), + rect.top + sal_Int32(rSourceRect.getHeight()+1)); + const bool bClearRightColumn( rect.right < maSize.getX() ); + const bool bClearBottomRow( rect.bottom < maSize.getY() ); + + if(SUCCEEDED(mpTexture->LockRect(0,&aLockedRect,&rect,D3DLOCK_NOSYSLOCK))) + { + if(sal_uInt8* pImage = rSource.lock()) + { + switch( rSource.getFormat() ) + { + case ::canvas::IColorBuffer::Format::A8R8G8B8: + { + const std::size_t nSourceBytesPerPixel(4); + const std::size_t nSourcePitchInBytes(rSource.getStride()); + pImage += rSourceRect.getMinY()*nSourcePitchInBytes; + pImage += rSourceRect.getMinX()*nSourceBytesPerPixel; + + // calculate the destination memory address + sal_uInt8 *pDst = static_cast<sal_uInt8*>(aLockedRect.pBits); + + const sal_uInt32 nNumBytesToCopy( + static_cast<sal_uInt32>( + rSourceRect.getWidth())* + nSourceBytesPerPixel); + const sal_uInt64 nNumLines(rSourceRect.getHeight()); + + for(sal_uInt64 i=0; i<nNumLines; ++i) + { + memcpy(pDst,pImage,nNumBytesToCopy); + + if( bClearRightColumn ) + { + // to avoid interpolation artifacts + // from other textures, the surface + // manager allocates one pixel gap + // between them. Clear that to + // transparent. + pDst[nNumBytesToCopy] = + pDst[nNumBytesToCopy+1] = + pDst[nNumBytesToCopy+2] = + pDst[nNumBytesToCopy+3] = 0x00; + } + pDst += aLockedRect.Pitch; + pImage += nSourcePitchInBytes; + } + + if( bClearBottomRow ) + memset(pDst, 0, nNumBytesToCopy+4); + } + break; + + case ::canvas::IColorBuffer::Format::X8R8G8B8: + { + const std::size_t nSourceBytesPerPixel(4); + const std::size_t nSourcePitchInBytes(rSource.getStride()); + pImage += rSourceRect.getMinY()*nSourcePitchInBytes; + pImage += rSourceRect.getMinX()*nSourceBytesPerPixel; + + // calculate the destination memory address + sal_uInt8 *pDst = static_cast<sal_uInt8*>(aLockedRect.pBits); + + const sal_Int32 nNumLines( + sal::static_int_cast<sal_Int32>(rSourceRect.getHeight())); + const sal_Int32 nNumColumns( + sal::static_int_cast<sal_Int32>(rSourceRect.getWidth())); + for(sal_Int32 i=0; i<nNumLines; ++i) + { + sal_uInt32 *pSrc32 = reinterpret_cast<sal_uInt32 *>(pImage); + sal_uInt32 *pDst32 = reinterpret_cast<sal_uInt32 *>(pDst); + for(sal_Int32 j=0; j<nNumColumns; ++j) + pDst32[j] = 0xFF000000 | pSrc32[j]; + + if( bClearRightColumn ) + pDst32[nNumColumns] = 0xFF000000; + + pDst += aLockedRect.Pitch; + pImage += nSourcePitchInBytes; + } + + if( bClearBottomRow ) + memset(pDst, 0, 4*(nNumColumns+1)); + } + break; + + default: + ENSURE_OR_RETURN_FALSE(false, + "DXSurface::update(): Unknown/unimplemented buffer format" ); + break; + } + + rSource.unlock(); + } + + return SUCCEEDED(mpTexture->UnlockRect(0)); + } + + return true; + } + + + // DXSurface::getSize + + + ::basegfx::B2IVector DXSurface::getSize() + { + return maSize; + } + + // DXRenderModule::DXRenderModule + + + DXRenderModule::DXRenderModule( const vcl::Window& rWindow ) : + mhWnd(nullptr), + mpDevice(), + mpDirect3D9(), + mpSwapChain(), + mpVertexBuffer(), + mpTexture(), + maSize(), + maVertexCache(), + mnCount(0), + mnBeginSceneCount(0), + mbCanUseDynamicTextures(false), + mbError( false ), + meType( PrimitiveType::Unknown ), + maPageSize(), + mad3dpp(), + maNumVertices( VERTEX_BUFFER_SIZE ), + maWriteIndex(0), + maReadIndex(0) + { + // TODO(P2): get rid of those fine-grained locking + ::osl::MutexGuard aGuard( maMutex ); + + if(!(create(rWindow))) + { + throw lang::NoSupportException( "Could not create DirectX device!" ); + } + + // allocate a single texture surface which can be used later. + // we also use this to calibrate the page size. + ::basegfx::B2IVector aPageSize(maPageSize); + while(true) + { + mpTexture = std::make_shared<DXSurface>(*this,aPageSize); + if(mpTexture->isValid()) + break; + + aPageSize.setX(aPageSize.getX()>>1); + aPageSize.setY(aPageSize.getY()>>1); + if((aPageSize.getX() < MIN_TEXTURE_SIZE) || + (aPageSize.getY() < MIN_TEXTURE_SIZE)) + { + throw lang::NoSupportException( + "Could not create DirectX device - insufficient texture space!" ); + } + } + maPageSize=aPageSize; + + IDirect3DVertexBuffer9 *pVB(nullptr); + if( FAILED(mpDevice->CreateVertexBuffer(sizeof(dxvertex)*maNumVertices, + D3DUSAGE_DYNAMIC|D3DUSAGE_WRITEONLY, + D3DFVF_XYZRHW|D3DFVF_DIFFUSE|D3DFVF_TEX1, + D3DPOOL_DEFAULT, + &pVB, + nullptr)) ) + { + throw lang::NoSupportException( + "Could not create DirectX device - out of memory!" ); + } + + mpVertexBuffer = sal::systools::COMReference<IDirect3DVertexBuffer9>(pVB, false); + } + + + // DXRenderModule::~DXRenderModule + + + DXRenderModule::~DXRenderModule() + { + disposing(); + } + + + // DXRenderModule::disposing + + + void DXRenderModule::disposing() + { + if(!mhWnd) + return; + + mpTexture.reset(); + mpWindow.disposeAndClear(); + mhWnd=nullptr; + + // refrain from releasing the DX9 objects. We're the only + // ones holding references to them, and it might be + // dangerous to destroy the DX9 device, before all other + // objects are dead. + } + + + // DXRenderModule::create + + + bool DXRenderModule::create( const vcl::Window& rWindow ) + { + // TODO(P2): get rid of those fine-grained locking + ::osl::MutexGuard aGuard( maMutex ); + + // TODO(F2): since we would like to share precious hardware + // resources, the direct3d9 object should be global. each new + // request for a canvas should only create a new swapchain. + mpDirect3D9 = sal::systools::COMReference<IDirect3D9>( + Direct3DCreate9(D3D_SDK_VERSION), false); + if(!mpDirect3D9.is()) + return false; + + maVertexCache.reserve( 1024 ); + + mpWindow.disposeAndClear(); + mpWindow.reset( VclPtr<SystemChildWindow>::Create( + const_cast<vcl::Window *>(&rWindow), 0) ); + + // system child window must not receive mouse events + mpWindow->SetMouseTransparent( true ); + + // parent should receive paint messages as well + mpWindow->SetParentClipMode(ParentClipMode::NoClip); + + // the system child window must not clear its background + mpWindow->EnableEraseBackground( false ); + + mpWindow->SetControlForeground(); + mpWindow->SetControlBackground(); + + const SystemEnvData *pData = mpWindow->GetSystemData(); + const HWND hwnd(reinterpret_cast<HWND>(pData->hWnd)); + mhWnd = hwnd; + + ENSURE_OR_THROW( IsWindow( mhWnd ), + "DXRenderModule::create() No valid HWND given." ); + + // retrieve position and size of the parent window + const ::Size &rSizePixel(rWindow.GetSizePixel()); + + // remember the size of the parent window, since we + // need to use this for our child window. + maSize.setX(static_cast<sal_Int32>(rSizePixel.Width())); + maSize.setY(static_cast<sal_Int32>(rSizePixel.Height())); + + // let the child window cover the same size as the parent window. + mpWindow->setPosSizePixel(0,0,maSize.getX(),maSize.getY()); + + // create a device from the direct3d9 object. + if(!(createDevice())) + { + mpWindow.disposeAndClear(); + return false; + } + + mpWindow->Show(); + + return true; + } + + + // DXRenderModule::verifyDevice + + + bool DXRenderModule::verifyDevice( const UINT nAdapter ) + { + ENSURE_OR_THROW( mpDirect3D9.is(), + "DXRenderModule::verifyDevice() No valid device." ); + + // ask direct3d9 about the capabilities of hardware devices on a specific adapter. + // here we decide if the underlying hardware of the machine 'is good enough'. + // since we only need a tiny little fraction of what could be used, this + // is basically a no-op. + D3DCAPS9 aCaps; + if(FAILED(mpDirect3D9->GetDeviceCaps(nAdapter,D3DDEVTYPE_HAL,&aCaps))) + return false; + if(!(aCaps.MaxTextureWidth)) + return false; + if(!(aCaps.MaxTextureHeight)) + return false; + maPageSize = ::basegfx::B2IVector(aCaps.MaxTextureWidth,aCaps.MaxTextureHeight); + + // check device against allow & denylist entries + D3DADAPTER_IDENTIFIER9 aIdent; + if(FAILED(mpDirect3D9->GetAdapterIdentifier(nAdapter,0,&aIdent))) + return false; + + DXCanvasItem aConfigItem; + DXCanvasItem::DeviceInfo aInfo; + aInfo.nVendorId = aIdent.VendorId; + aInfo.nDeviceId = aIdent.DeviceId; + aInfo.nDeviceSubSysId = aIdent.SubSysId; + aInfo.nDeviceRevision = aIdent.Revision; + + aInfo.nDriverId = HIWORD(aIdent.DriverVersion.HighPart); + aInfo.nDriverVersion = LOWORD(aIdent.DriverVersion.HighPart); + aInfo.nDriverSubVersion = HIWORD(aIdent.DriverVersion.LowPart); + aInfo.nDriverBuildId = LOWORD(aIdent.DriverVersion.LowPart); + + if( !aConfigItem.isDeviceUsable(aInfo) ) + return false; + + if( aConfigItem.isDenylistCurrentDevice() ) + { + aConfigItem.denylistDevice(aInfo); + return false; + } + + aConfigItem.adaptMaxTextureSize(maPageSize); + + mbCanUseDynamicTextures = (aCaps.Caps2 & D3DCAPS2_DYNAMICTEXTURES) != 0; + + return true; + } + + + // DXRenderModule::createDevice + + + bool DXRenderModule::createDevice() + { + // we expect that the caller provides us with a valid HWND + ENSURE_OR_THROW( IsWindow(mhWnd), + "DXRenderModule::createDevice() No valid HWND given." ); + + // we expect that the caller already created the direct3d9 object. + ENSURE_OR_THROW( mpDirect3D9.is(), + "DXRenderModule::createDevice() no direct3d?." ); + + // find the adapter identifier from the window. + const UINT aAdapter(getAdapterFromWindow()); + if(aAdapter == static_cast<UINT>(-1)) + return false; + + // verify that device possibly works + if( !verifyDevice(aAdapter) ) + return false; + + // query the display mode from the selected adapter. + // we'll later request the backbuffer format to be same + // same as the display format. + D3DDISPLAYMODE d3ddm; + mpDirect3D9->GetAdapterDisplayMode(aAdapter,&d3ddm); + + // we need to use D3DSWAPEFFECT_COPY here since the canvas-api has + // basically nothing to do with efficient resource handling. it tries + // to avoid drawing whenever possible, which is simply not the most + // efficient way we could leverage the hardware in this case. it would + // be far better to redraw the backbuffer each time we would like to + // display the content of the backbuffer, but we need to face reality + // here and follow how the canvas was designed. + + // Strictly speaking, we don't need a full screen worth of + // backbuffer here. We could also scale dynamically with + // the current window size, but this will make it + // necessary to temporarily have two buffers while copying + // from the old to the new one. What's more, at the time + // we need a larger buffer, DX might not have sufficient + // resources available, and we're then left with too small + // a back buffer, and no way of falling back to a + // different canvas implementation. + ZeroMemory( &mad3dpp, sizeof(mad3dpp) ); + mad3dpp.BackBufferWidth = std::max(maSize.getX(), + sal_Int32(d3ddm.Width)); + mad3dpp.BackBufferHeight = std::max(maSize.getY(), + sal_Int32(d3ddm.Height)); + mad3dpp.BackBufferCount = 1; + mad3dpp.Windowed = TRUE; + mad3dpp.SwapEffect = D3DSWAPEFFECT_COPY; + mad3dpp.BackBufferFormat = d3ddm.Format; + mad3dpp.EnableAutoDepthStencil = FALSE; + mad3dpp.hDeviceWindow = mhWnd; + mad3dpp.PresentationInterval = D3DPRESENT_INTERVAL_ONE; + + // now create the device, first try hardware vertex processing, + // then software vertex processing. if both queries fail, we give up + // and indicate failure. + IDirect3DDevice9 *pDevice(nullptr); + if(FAILED(mpDirect3D9->CreateDevice(aAdapter, + D3DDEVTYPE_HAL, + mhWnd, + D3DCREATE_HARDWARE_VERTEXPROCESSING| + D3DCREATE_MULTITHREADED|D3DCREATE_FPU_PRESERVE, + &mad3dpp, + &pDevice))) + if(FAILED(mpDirect3D9->CreateDevice(aAdapter, + D3DDEVTYPE_HAL, + mhWnd, + D3DCREATE_SOFTWARE_VERTEXPROCESSING| + D3DCREATE_MULTITHREADED|D3DCREATE_FPU_PRESERVE, + &mad3dpp, + &pDevice))) + return false; + + // got it, store it in a safe place... + mpDevice = sal::systools::COMReference<IDirect3DDevice9>(pDevice, false); + + // After CreateDevice, the first swap chain already exists, so just get it... + IDirect3DSwapChain9 *pSwapChain(nullptr); + pDevice->GetSwapChain(0,&pSwapChain); + mpSwapChain = sal::systools::COMReference<IDirect3DSwapChain9>(pSwapChain, false); + if( !mpSwapChain.is() ) + return false; + + // clear the render target [which is the backbuffer in this case]. + // we are forced to do this once, and furthermore right now. + // please note that this is only possible since we created the + // backbuffer with copy semantics [the content is preserved after + // calls to Present()], which is an unnecessarily expensive operation. + LPDIRECT3DSURFACE9 pBackBuffer = nullptr; + mpSwapChain->GetBackBuffer(0,D3DBACKBUFFER_TYPE_MONO,&pBackBuffer); + mpDevice->SetRenderTarget( 0, pBackBuffer ); + mpDevice->Clear(0,nullptr,D3DCLEAR_TARGET,0,1.0f,0); + pBackBuffer->Release(); + + return true; + } + + + // DXRenderModule::createSystemMemorySurface + + + sal::systools::COMReference<IDirect3DSurface9> DXRenderModule::createSystemMemorySurface( const ::basegfx::B2IVector& rSize ) + { + if(isDisposed()) + return sal::systools::COMReference<IDirect3DSurface9>(nullptr); + + // please note that D3DFMT_X8R8G8B8 is the only format we're + // able to choose here, since GetDC() doesn't support any + // other 32bit-format. + IDirect3DSurface9 *pSurface(nullptr); + if( FAILED(mpDevice->CreateOffscreenPlainSurface( + rSize.getX(), + rSize.getY(), + D3DFMT_X8R8G8B8, + D3DPOOL_SYSTEMMEM, + &pSurface, + nullptr)) ) + { + throw lang::NoSupportException( + "Could not create offscreen surface - out of mem!" ); + } + + return sal::systools::COMReference<IDirect3DSurface9>(pSurface, false); + } + + + // DXRenderModule::flip + + + bool DXRenderModule::flip( const ::basegfx::B2IRectangle& rUpdateArea, + const ::basegfx::B2IRectangle& /*rCurrWindowArea*/ ) + { + // TODO(P2): get rid of those fine-grained locking + ::osl::MutexGuard aGuard( maMutex ); + + if(isDisposed() || !mpSwapChain.is()) + return false; + + flushVertexCache(); + + // TODO(P2): Might be faster to actually pass update area here + RECT aRect = + { + rUpdateArea.getMinX(), + rUpdateArea.getMinY(), + rUpdateArea.getMaxX(), + rUpdateArea.getMaxY() + }; + HRESULT hr(mpSwapChain->Present(&aRect,&aRect,nullptr,nullptr,0)); + if(FAILED(hr)) + { + if(hr != D3DERR_DEVICELOST) + return false; + + // interestingly enough, sometimes the Reset() below + // *still* causes DeviceLost errors. So, cycle until + // DX was kind enough to really reset the device... + do + { + mpVertexBuffer.clear(); + hr = mpDevice->Reset(&mad3dpp); + if(SUCCEEDED(hr)) + { + IDirect3DVertexBuffer9 *pVB(nullptr); + if( FAILED(mpDevice->CreateVertexBuffer(sizeof(dxvertex)*maNumVertices, + D3DUSAGE_DYNAMIC|D3DUSAGE_WRITEONLY, + D3DFVF_XYZRHW|D3DFVF_DIFFUSE|D3DFVF_TEX1, + D3DPOOL_DEFAULT, + &pVB, + nullptr)) ) + { + throw lang::NoSupportException( + "Could not create DirectX device - out of memory!" ); + } + mpVertexBuffer = sal::systools::COMReference<IDirect3DVertexBuffer9>(pVB, false); + + // retry after the restore + if(SUCCEEDED(mpSwapChain->Present(&aRect,&aRect,nullptr,nullptr,0))) + return true; + } + + osl::Thread::wait(std::chrono::seconds(1)); + } + while(hr == D3DERR_DEVICELOST); + + return false; + } + + return true; + } + + + // DXRenderModule::screenShot + + + void DXRenderModule::screenShot() + { + } + + + // DXRenderModule::resize + + + void DXRenderModule::resize( const ::basegfx::B2IRange& rect ) + { + // TODO(P2): get rid of those fine-grained locking + ::osl::MutexGuard aGuard( maMutex ); + + if(isDisposed()) + return; + + // don't do anything if the size didn't change. + if(maSize.getX() == static_cast<sal_Int32>(rect.getWidth()) && + maSize.getY() == static_cast<sal_Int32>(rect.getHeight())) + return; + + // TODO(Q2): use numeric cast to prevent overflow + maSize.setX(static_cast<sal_Int32>(rect.getWidth())); + maSize.setY(static_cast<sal_Int32>(rect.getHeight())); + + mpWindow->setPosSizePixel(0,0,maSize.getX(),maSize.getY()); + + // resize back buffer, if necessary + + + // don't attempt to create anything if the + // requested size is NULL. + if(!(maSize.getX())) + return; + if(!(maSize.getY())) + return; + + // backbuffer too small (might happen, if window is + // maximized across multiple monitors) + if( sal_Int32(mad3dpp.BackBufferWidth) < maSize.getX() || + sal_Int32(mad3dpp.BackBufferHeight) < maSize.getY() ) + { + mad3dpp.BackBufferWidth = maSize.getX(); + mad3dpp.BackBufferHeight = maSize.getY(); + + // clear before, save resources + mpSwapChain.clear(); + + IDirect3DSwapChain9 *pSwapChain(nullptr); + if(FAILED(mpDevice->CreateAdditionalSwapChain(&mad3dpp,&pSwapChain))) + return; + mpSwapChain = sal::systools::COMReference<IDirect3DSwapChain9>(pSwapChain, false); + + // clear the render target [which is the backbuffer in this case]. + // we are forced to do this once, and furthermore right now. + // please note that this is only possible since we created the + // backbuffer with copy semantics [the content is preserved after + // calls to Present()], which is an unnecessarily expensive operation. + LPDIRECT3DSURFACE9 pBackBuffer = nullptr; + mpSwapChain->GetBackBuffer(0,D3DBACKBUFFER_TYPE_MONO,&pBackBuffer); + mpDevice->SetRenderTarget( 0, pBackBuffer ); + mpDevice->Clear(0,nullptr,D3DCLEAR_TARGET,0,1.0f,0); + pBackBuffer->Release(); + } + } + + + // DXRenderModule::getPageSize + + + ::basegfx::B2IVector DXRenderModule::getPageSize() + { + // TODO(P2): get rid of those fine-grained locking + ::osl::MutexGuard aGuard( maMutex ); + return maPageSize; + } + + + // DXRenderModule::createSurface + + + std::shared_ptr<canvas::ISurface> DXRenderModule::createSurface( const ::basegfx::B2IVector& surfaceSize ) + { + // TODO(P2): get rid of those fine-grained locking + ::osl::MutexGuard aGuard( maMutex ); + + if(isDisposed()) + return std::shared_ptr<canvas::ISurface>(); + + const ::basegfx::B2IVector& rPageSize( getPageSize() ); + ::basegfx::B2ISize aSize(surfaceSize); + if(!(aSize.getX())) + aSize.setX(rPageSize.getX()); + if(!(aSize.getY())) + aSize.setY(rPageSize.getY()); + + if(mpTexture.use_count() == 1) + return mpTexture; + + return std::make_shared<DXSurface>(*this,aSize); + } + + + // DXRenderModule::beginPrimitive + + + void DXRenderModule::beginPrimitive( PrimitiveType eType ) + { + // TODO(P2): get rid of those fine-grained locking + ::osl::MutexGuard aGuard( maMutex ); + + if(isDisposed()) + return; + + ENSURE_OR_THROW( !mnBeginSceneCount, + "DXRenderModule::beginPrimitive(): nested call" ); + + ++mnBeginSceneCount; + meType=eType; + mnCount=0; + } + + + // DXRenderModule::endPrimitive + + + void DXRenderModule::endPrimitive() + { + // TODO(P2): get rid of those fine-grained locking + ::osl::MutexGuard aGuard( maMutex ); + + if(isDisposed()) + return; + + --mnBeginSceneCount; + meType = PrimitiveType::Unknown; + mnCount = 0; + } + + + // DXRenderModule::pushVertex + + + void DXRenderModule::pushVertex( const ::canvas::Vertex& vertex ) + { + // TODO(P2): get rid of those fine-grained locking + ::osl::MutexGuard aGuard( maMutex ); + + if(isDisposed()) + return; + + switch(meType) + { + case PrimitiveType::Triangle: + { + maVertexCache.push_back(vertex); + ++mnCount; + mnCount &= 3; + break; + } + + case PrimitiveType::Quad: + { + if(mnCount == 3) + { + const std::size_t size(maVertexCache.size()); + ::canvas::Vertex v0(maVertexCache[size-1]); + ::canvas::Vertex v2(maVertexCache[size-3]); + maVertexCache.push_back(v0); + maVertexCache.push_back(vertex); + maVertexCache.push_back(v2); + mnCount=0; + } + else + { + maVertexCache.push_back(vertex); + ++mnCount; + } + break; + } + + default: + SAL_WARN("canvas.directx", "DXRenderModule::pushVertex(): unexpected primitive type"); + break; + } + } + + + // DXRenderModule::isError + + + bool DXRenderModule::isError() + { + // TODO(P2): get rid of those fine-grained locking + ::osl::MutexGuard aGuard( maMutex ); + + return mbError; + } + + + // DXRenderModule::getAdapterFromWindow + + + UINT DXRenderModule::getAdapterFromWindow() + { + HMONITOR hMonitor(MonitorFromWindow(mhWnd, MONITOR_DEFAULTTONEAREST)); + UINT aAdapterCount(mpDirect3D9->GetAdapterCount()); + for(UINT i=0; i<aAdapterCount; ++i) + if(hMonitor == mpDirect3D9->GetAdapterMonitor(i)) + return i; + return static_cast<UINT>(-1); + } + + + // DXRenderModule::commitVertexCache + + + void DXRenderModule::commitVertexCache() + { + if(maReadIndex != maWriteIndex) + { + const std::size_t nVertexStride = sizeof(dxvertex); + const unsigned int nNumVertices = maWriteIndex-maReadIndex; + const unsigned int nNumPrimitives = nNumVertices / 3; + + if(FAILED(mpDevice->SetStreamSource(0,mpVertexBuffer.get(),0,nVertexStride))) + return; + + if(FAILED(mpDevice->SetFVF(D3DFVF_XYZRHW|D3DFVF_DIFFUSE|D3DFVF_TEX1))) + return; + + if(FAILED(mpDevice->BeginScene())) + return; + + mbError |= FAILED(mpDevice->DrawPrimitive(D3DPT_TRIANGLELIST,maReadIndex,nNumPrimitives)); + mbError |= FAILED(mpDevice->EndScene()); + + maReadIndex += nNumVertices; + } + } + + + // DXRenderModule::flushVertexCache + + + void DXRenderModule::flushVertexCache() + { + if(maVertexCache.empty()) + return; + + mbError=true; + + if( FAILED(mpDevice->SetRenderState(D3DRS_LIGHTING,FALSE))) + return; + + // enable texture alpha blending + if( FAILED(mpDevice->SetRenderState(D3DRS_ALPHABLENDENABLE,TRUE))) + return; + + mpDevice->SetSamplerState(0,D3DSAMP_MAGFILTER,D3DTEXF_LINEAR); + mpDevice->SetSamplerState(0,D3DSAMP_MINFILTER,D3DTEXF_LINEAR); + mpDevice->SetSamplerState(0,D3DSAMP_ADDRESSU ,D3DTADDRESS_CLAMP ); + mpDevice->SetSamplerState(0,D3DSAMP_ADDRESSV ,D3DTADDRESS_CLAMP ); + + // configure the fixed-function pipeline. + // the only 'feature' we need here is to modulate the alpha-channels + // from the texture and the interpolated diffuse color. the result + // will then be blended with the backbuffer. + // fragment color = texture color * diffuse.alpha. + mpDevice->SetTextureStageState(0,D3DTSS_ALPHAOP,D3DTOP_MODULATE); + mpDevice->SetTextureStageState(0,D3DTSS_ALPHAARG1,D3DTA_TEXTURE); + mpDevice->SetTextureStageState(0,D3DTSS_ALPHAARG2,D3DTA_DIFFUSE); + + // normal combination of object... + if( FAILED(mpDevice->SetRenderState(D3DRS_SRCBLEND,D3DBLEND_SRCALPHA)) ) + return; + + // ..and background color + if( FAILED(mpDevice->SetRenderState(D3DRS_DESTBLEND,D3DBLEND_INVSRCALPHA)) ) + return; + + // disable backface culling; this enables us to mirror sprites + // by simply reverting the triangles, which, with enabled + // culling, would be invisible otherwise + if( FAILED(mpDevice->SetRenderState(D3DRS_CULLMODE,D3DCULL_NONE)) ) + return; + + mbError=false; + + std::size_t nSize(maVertexCache.size()); + const std::size_t nVertexStride = sizeof(dxvertex); + + const ::basegfx::B2IVector aPageSize(getPageSize()); + const float nHalfPixelSizeX(0.5f/aPageSize.getX()); + const float nHalfPixelSizeY(0.5f/aPageSize.getY()); + vertexCache_t::const_iterator it(maVertexCache.begin()); + + while( nSize ) + { + DWORD dwLockFlags(D3DLOCK_NOOVERWRITE); + + // Check to see if there's space for the current set of + // vertices in the buffer. + if( maNumVertices - maWriteIndex < nSize ) + { + commitVertexCache(); + dwLockFlags = D3DLOCK_DISCARD; + maWriteIndex = 0; + maReadIndex = 0; + } + + dxvertex *vertices(nullptr); + const std::size_t nNumVertices( + std::min(maNumVertices - maWriteIndex, + nSize)); + if(FAILED(mpVertexBuffer->Lock(maWriteIndex*nVertexStride, + nNumVertices*nVertexStride, + reinterpret_cast<void **>(&vertices), + dwLockFlags))) + return; + + std::size_t nIndex(0); + while( nIndex < nNumVertices ) + { + dxvertex &dest = vertices[nIndex++]; + dest.x=it->x; + dest.y=it->y; + dest.z=it->z; + dest.rhw=1; + const sal_uInt32 alpha(static_cast<sal_uInt32>(it->a*255.0f)); + dest.diffuse=D3DCOLOR_ARGB(alpha,255,255,255); + dest.u=static_cast<float>(it->u + nHalfPixelSizeX); + dest.v=static_cast<float>(it->v + nHalfPixelSizeY); + ++it; + } + + mpVertexBuffer->Unlock(); + + // Advance to the next position in the vertex buffer. + maWriteIndex += nNumVertices; + nSize -= nNumVertices; + + commitVertexCache(); + } + + maVertexCache.clear(); + } + } + + + // createRenderModule + + + IDXRenderModuleSharedPtr createRenderModule( const vcl::Window& rParent ) + { + return std::make_shared<DXRenderModule>(rParent); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_bitmap.cxx b/canvas/source/directx/dx_bitmap.cxx new file mode 100644 index 000000000..2f42170d6 --- /dev/null +++ b/canvas/source/directx/dx_bitmap.cxx @@ -0,0 +1,204 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <memory> + +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/range/b2irange.hxx> +#include <tools/diagnose_ex.h> + +#include "dx_bitmap.hxx" +#include "dx_graphicsprovider.hxx" +#include "dx_impltools.hxx" + +using namespace ::com::sun::star; + +namespace dxcanvas +{ + + // DXBitmap::DXBitmap + + + DXBitmap::DXBitmap( const BitmapSharedPtr& rBitmap, + bool bWithAlpha ) : + mpGdiPlusUser( GDIPlusUser::createInstance() ), + maSize(rBitmap->GetWidth(),rBitmap->GetHeight()), + mpBitmap(rBitmap), + mpGraphics(tools::createGraphicsFromBitmap(mpBitmap)), + mbAlpha(bWithAlpha) + { + } + + DXBitmap::DXBitmap( const ::basegfx::B2IVector& rSize, + bool bWithAlpha ) : + mpGdiPlusUser( GDIPlusUser::createInstance() ), + maSize(rSize), + mpBitmap(), + mpGraphics(), + mbAlpha(bWithAlpha) + { + // create container for pixel data + if(mbAlpha) + { + mpBitmap = std::make_shared<Gdiplus::Bitmap>( + maSize.getX(), + maSize.getY(), + PixelFormat32bppARGB); + } + else + { + mpBitmap = std::make_shared<Gdiplus::Bitmap>( + maSize.getX(), + maSize.getY(), + PixelFormat24bppRGB); + } + + mpGraphics = tools::createGraphicsFromBitmap(mpBitmap); + } + + BitmapSharedPtr DXBitmap::getBitmap() const + { + return mpBitmap; + } + + GraphicsSharedPtr DXBitmap::getGraphics() + { + return mpGraphics; + } + + ::basegfx::B2IVector DXBitmap::getSize() const + { + return maSize; + } + + bool DXBitmap::hasAlpha() const + { + return mbAlpha; + } + + uno::Sequence< sal_Int8 > DXBitmap::getData( rendering::IntegerBitmapLayout& /*bitmapLayout*/, + const geometry::IntegerRectangle2D& rect ) + { + uno::Sequence< sal_Int8 > aRes( (rect.X2-rect.X1)*(rect.Y2-rect.Y1)*4 ); // TODO(F1): Be format-agnostic here + + const Gdiplus::Rect aRect( tools::gdiPlusRectFromIntegerRectangle2D( rect ) ); + + Gdiplus::BitmapData aBmpData; + aBmpData.Width = rect.X2-rect.X1; + aBmpData.Height = rect.Y2-rect.Y1; + aBmpData.Stride = 4*aBmpData.Width; + aBmpData.PixelFormat = PixelFormat32bppARGB; + aBmpData.Scan0 = aRes.getArray(); + + // TODO(F1): Support more pixel formats natively + + // read data from bitmap + if( Gdiplus::Ok != mpBitmap->LockBits( &aRect, + Gdiplus::ImageLockModeRead | Gdiplus::ImageLockModeUserInputBuf, + PixelFormat32bppARGB, // TODO(F1): Adapt to + // Graphics native + // format/change + // getMemoryLayout + &aBmpData ) ) + { + // failed to lock, bail out + return uno::Sequence< sal_Int8 >(); + } + + mpBitmap->UnlockBits( &aBmpData ); + + return aRes; + } + + void DXBitmap::setData( const uno::Sequence< sal_Int8 >& data, + const rendering::IntegerBitmapLayout& /*bitmapLayout*/, + const geometry::IntegerRectangle2D& rect ) + { + const Gdiplus::Rect aRect( tools::gdiPlusRectFromIntegerRectangle2D( rect ) ); + + Gdiplus::BitmapData aBmpData; + aBmpData.Width = rect.X2-rect.X1; + aBmpData.Height = rect.Y2-rect.Y1; + aBmpData.Stride = 4*aBmpData.Width; + aBmpData.PixelFormat = PixelFormat32bppARGB; + aBmpData.Scan0 = const_cast<sal_Int8 *>(data.getConstArray()); + // const_cast is safe, "Gdiplus::ImageLockModeWrite + // | Gdiplus::ImageLockModeUserInputBuf makes the data go from + // BitmapData into Bitmap", says Thorsten + + // TODO(F1): Support more pixel formats natively + + if( Gdiplus::Ok != mpBitmap->LockBits( &aRect, + Gdiplus::ImageLockModeWrite | Gdiplus::ImageLockModeUserInputBuf, + PixelFormat32bppARGB, // TODO: Adapt to + // Graphics native + // format/change + // getMemoryLayout + &aBmpData ) ) + { + throw uno::RuntimeException(); + } + + // commit data to bitmap + mpBitmap->UnlockBits( &aBmpData ); + } + + void DXBitmap::setPixel( const uno::Sequence< sal_Int8 >& color, + const rendering::IntegerBitmapLayout& /*bitmapLayout*/, + const geometry::IntegerPoint2D& pos ) + { + const geometry::IntegerSize2D aSize( maSize.getX(),maSize.getY() ); + + ENSURE_ARG_OR_THROW( pos.X >= 0 && pos.X < aSize.Width, + "CanvasHelper::setPixel: X coordinate out of bounds" ); + ENSURE_ARG_OR_THROW( pos.Y >= 0 && pos.Y < aSize.Height, + "CanvasHelper::setPixel: Y coordinate out of bounds" ); + ENSURE_ARG_OR_THROW( color.getLength() > 3, + "CanvasHelper::setPixel: not enough color components" ); + + if( Gdiplus::Ok != mpBitmap->SetPixel( pos.X, pos.Y, + Gdiplus::Color( tools::sequenceToArgb( color )))) + { + throw uno::RuntimeException(); + } + } + + uno::Sequence< sal_Int8 > DXBitmap::getPixel( rendering::IntegerBitmapLayout& /*bitmapLayout*/, + const geometry::IntegerPoint2D& pos ) + { + const geometry::IntegerSize2D aSize( maSize.getX(),maSize.getY() ); + + ENSURE_ARG_OR_THROW( pos.X >= 0 && pos.X < aSize.Width, + "CanvasHelper::getPixel: X coordinate out of bounds" ); + ENSURE_ARG_OR_THROW( pos.Y >= 0 && pos.Y < aSize.Height, + "CanvasHelper::getPixel: Y coordinate out of bounds" ); + + Gdiplus::Color aColor; + + if( Gdiplus::Ok != mpBitmap->GetPixel( pos.X, pos.Y, &aColor ) ) + return uno::Sequence< sal_Int8 >(); + + return tools::argbToIntSequence(aColor.GetValue()); + } + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_bitmap.hxx b/canvas/source/directx/dx_bitmap.hxx new file mode 100644 index 000000000..127ee5fc5 --- /dev/null +++ b/canvas/source/directx/dx_bitmap.hxx @@ -0,0 +1,84 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include <com/sun/star/rendering/XCanvas.hpp> +#include <com/sun/star/rendering/XIntegerBitmap.hpp> +#include <basegfx/vector/b2ivector.hxx> +#include <basegfx/point/b2dpoint.hxx> +#include <basegfx/range/b2drange.hxx> +#include <memory> +#include "dx_winstuff.hxx" +#include "dx_ibitmap.hxx" +#include "dx_graphicsprovider.hxx" +#include "dx_gdiplususer.hxx" + +namespace dxcanvas +{ + class DXBitmap : public IBitmap + { + public: + DXBitmap( const BitmapSharedPtr& rBitmap, + bool bWithAlpha ); + DXBitmap( const ::basegfx::B2IVector& rSize, + bool bWithAlpha ); + + virtual GraphicsSharedPtr getGraphics() override; + + virtual BitmapSharedPtr getBitmap() const override; + virtual ::basegfx::B2IVector getSize() const override; + virtual bool hasAlpha() const override; + + css::uno::Sequence< sal_Int8 > getData( + css::rendering::IntegerBitmapLayout& bitmapLayout, + const css::geometry::IntegerRectangle2D& rect ) override; + + void setData( + const css::uno::Sequence< sal_Int8 >& data, + const css::rendering::IntegerBitmapLayout& bitmapLayout, + const css::geometry::IntegerRectangle2D& rect ) override; + + void setPixel( + const css::uno::Sequence< sal_Int8 >& color, + const css::rendering::IntegerBitmapLayout& bitmapLayout, + const css::geometry::IntegerPoint2D& pos ) override; + + css::uno::Sequence< sal_Int8 > getPixel( + css::rendering::IntegerBitmapLayout& bitmapLayout, + const css::geometry::IntegerPoint2D& pos ) override; + + private: + // Refcounted global GDI+ state container + GDIPlusUserSharedPtr mpGdiPlusUser; + + // size of this image in pixels [integral unit] + ::basegfx::B2IVector maSize; + + BitmapSharedPtr mpBitmap; + GraphicsSharedPtr mpGraphics; + + // true if the bitmap contains an alpha channel + bool mbAlpha; + }; + + typedef std::shared_ptr< DXBitmap > DXBitmapSharedPtr; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_bitmapcanvashelper.cxx b/canvas/source/directx/dx_bitmapcanvashelper.cxx new file mode 100644 index 000000000..9733853f5 --- /dev/null +++ b/canvas/source/directx/dx_bitmapcanvashelper.cxx @@ -0,0 +1,222 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> +#include <sal/log.hxx> + +#include <algorithm> + +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/point/b2dpoint.hxx> +#include <basegfx/utils/canvastools.hxx> +#include <com/sun/star/rendering/CompositeOperation.hpp> +#include <com/sun/star/rendering/PathCapType.hpp> +#include <com/sun/star/rendering/PathJoinType.hpp> +#include <com/sun/star/rendering/RepaintResult.hpp> +#include <com/sun/star/rendering/TexturingMode.hpp> +#include <rtl/math.hxx> +#include <tools/diagnose_ex.h> + +#include <canvas/canvastools.hxx> + +#include "dx_bitmapcanvashelper.hxx" +#include "dx_canvasfont.hxx" +#include "dx_impltools.hxx" +#include "dx_spritecanvas.hxx" +#include "dx_textlayout.hxx" + + +using namespace ::com::sun::star; + +namespace dxcanvas +{ + BitmapCanvasHelper::BitmapCanvasHelper() : + mpTarget() + {} + + void BitmapCanvasHelper::disposing() + { + mpTarget.reset(); + CanvasHelper::disposing(); + } + + void BitmapCanvasHelper::setTarget( const IBitmapSharedPtr& rTarget ) + { + ENSURE_OR_THROW( rTarget, + "BitmapCanvasHelper::setTarget(): Invalid target" ); + ENSURE_OR_THROW( !mpTarget, + "BitmapCanvasHelper::setTarget(): target set, old target would be overwritten" ); + + mpTarget = rTarget; + CanvasHelper::setTarget(rTarget); + } + + void BitmapCanvasHelper::setTarget( const IBitmapSharedPtr& rTarget, + const ::basegfx::B2ISize& rOutputOffset ) + { + ENSURE_OR_THROW( rTarget, + "BitmapCanvasHelper::setTarget(): invalid target" ); + ENSURE_OR_THROW( !mpTarget, + "BitmapCanvasHelper::setTarget(): target set, old target would be overwritten" ); + + mpTarget = rTarget; + CanvasHelper::setTarget(rTarget,rOutputOffset); + } + + void BitmapCanvasHelper::clear() + { + if( needOutput() ) + { + GraphicsSharedPtr pGraphics( mpTarget->getGraphics() ); + + Gdiplus::Color aClearColor = hasAlpha() ? + Gdiplus::Color( 0,255,255,255 ) : Gdiplus::Color(Gdiplus::ARGB(Gdiplus::Color::White)); + + ENSURE_OR_THROW( + Gdiplus::Ok == pGraphics->SetCompositingMode( + Gdiplus::CompositingModeSourceCopy ), // force set, don't blend + "BitmapCanvasHelper::clear(): GDI+ SetCompositingMode call failed" ); + ENSURE_OR_THROW( + Gdiplus::Ok == pGraphics->Clear( aClearColor ), + "BitmapCanvasHelper::clear(): GDI+ Clear call failed" ); + } + } + + uno::Reference< rendering::XCachedPrimitive > BitmapCanvasHelper::drawTextLayout( const rendering::XCanvas* /*pCanvas*/, + const uno::Reference< rendering::XTextLayout >& xLayoutetText, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState ) + { + ENSURE_OR_THROW( xLayoutetText.is(), + "BitmapCanvasHelper::drawTextLayout: layout is NULL"); + + if( needOutput() ) + { + TextLayout* pTextLayout = + dynamic_cast< TextLayout* >( xLayoutetText.get() ); + + ENSURE_OR_THROW( pTextLayout, + "BitmapCanvasHelper::drawTextLayout(): TextLayout not compatible with this canvas" ); + + pTextLayout->draw( mpTarget->getGraphics(), + viewState, + renderState, + maOutputOffset, + mpDevice, + mpTarget->hasAlpha() ); + } + + return uno::Reference< rendering::XCachedPrimitive >(nullptr); + } + + void BitmapCanvasHelper::copyRect( const rendering::XCanvas* /*pCanvas*/, + const uno::Reference< rendering::XBitmapCanvas >& /*sourceCanvas*/, + const geometry::RealRectangle2D& /*sourceRect*/, + const rendering::ViewState& /*sourceViewState*/, + const rendering::RenderState& /*sourceRenderState*/, + const geometry::RealRectangle2D& /*destRect*/, + const rendering::ViewState& /*destViewState*/, + const rendering::RenderState& /*destRenderState*/ ) + { + // TODO(F2): copyRect NYI + } + + geometry::IntegerSize2D BitmapCanvasHelper::getSize() + { + if( !mpTarget ) + return geometry::IntegerSize2D(1, 1); + + return basegfx::unotools::integerSize2DFromB2ISize(mpTarget->getSize()); + } + + uno::Reference< rendering::XBitmap > BitmapCanvasHelper::getScaledBitmap( const geometry::RealSize2D& /*newSize*/, + bool /*beFast*/ ) + { + // TODO(F1): + return uno::Reference< rendering::XBitmap >(); + } + + uno::Sequence< sal_Int8 > BitmapCanvasHelper::getData( rendering::IntegerBitmapLayout& bitmapLayout, + const geometry::IntegerRectangle2D& rect ) + { + SAL_INFO( "canvas.directx", "::dxcanvas::BitmapCanvasHelper::getData()" ); + + ENSURE_OR_THROW( mpTarget, + "::dxcanvas::BitmapCanvasHelper::getData(): disposed" ); + + bitmapLayout = getMemoryLayout(); + return mpTarget->getData(bitmapLayout,rect); + } + + void BitmapCanvasHelper::setData( const uno::Sequence< sal_Int8 >& data, + const rendering::IntegerBitmapLayout& bitmapLayout, + const geometry::IntegerRectangle2D& rect ) + { + SAL_INFO( "canvas.directx", "::dxcanvas::BitmapCanvasHelper::setData()" ); + + ENSURE_OR_THROW( mpTarget, + "::dxcanvas::BitmapCanvasHelper::setData(): disposed" ); + + mpTarget->setData(data,bitmapLayout,rect); + } + + void BitmapCanvasHelper::setPixel( const uno::Sequence< sal_Int8 >& color, + const rendering::IntegerBitmapLayout& bitmapLayout, + const geometry::IntegerPoint2D& pos ) + { + SAL_INFO( "canvas.directx", "::dxcanvas::BitmapCanvasHelper::setPixel()" ); + + ENSURE_OR_THROW( mpTarget, + "::dxcanvas::BitmapCanvasHelper::setPixel(): disposed" ); + + mpTarget->setPixel(color,bitmapLayout,pos); + } + + uno::Sequence< sal_Int8 > BitmapCanvasHelper::getPixel( rendering::IntegerBitmapLayout& bitmapLayout, + const geometry::IntegerPoint2D& pos ) + { + SAL_INFO( "canvas.directx", "::dxcanvas::BitmapCanvasHelper::getPixel()" ); + + ENSURE_OR_THROW( mpTarget, + "::dxcanvas::BitmapCanvasHelper::getPixel(): disposed" ); + + bitmapLayout = getMemoryLayout(); + return mpTarget->getPixel(bitmapLayout,pos); + } + + uno::Reference< rendering::XBitmapPalette > BitmapCanvasHelper::getPalette() + { + // TODO(F1): Palette bitmaps NYI + return uno::Reference< rendering::XBitmapPalette >(); + } + + rendering::IntegerBitmapLayout BitmapCanvasHelper::getMemoryLayout() + { + if( !mpTarget ) + return rendering::IntegerBitmapLayout(); // we're disposed + + return ::canvas::tools::getStdMemoryLayout(getSize()); + } + bool BitmapCanvasHelper::hasAlpha() const + { + return mpTarget && mpTarget->hasAlpha(); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_bitmapcanvashelper.hxx b/canvas/source/directx/dx_bitmapcanvashelper.hxx new file mode 100644 index 000000000..46f970493 --- /dev/null +++ b/canvas/source/directx/dx_bitmapcanvashelper.hxx @@ -0,0 +1,126 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include <com/sun/star/rendering/XCanvas.hpp> +#include <com/sun/star/rendering/XBitmapCanvas.hpp> + +#include <basegfx/vector/b2isize.hxx> +#include <basegfx/vector/b2dsize.hxx> + +#include "dx_graphicsprovider.hxx" +#include "dx_ibitmap.hxx" +#include "dx_gdiplususer.hxx" +#include "dx_impltools.hxx" +#include "dx_canvashelper.hxx" + + +namespace dxcanvas +{ + /** Helper class for basic canvas functionality. Also offers + optional backbuffer painting, when providing it with a second + HDC to render into. + */ + class BitmapCanvasHelper : public CanvasHelper + { + public: + BitmapCanvasHelper(); + + /// Release all references + void disposing(); + + /** Set the target for rendering operations + + @param rTarget + Render target + */ + void setTarget( const IBitmapSharedPtr& rTarget ); + + /** Set the target for rendering operations + + @param rTarget + Render target + + @param rOutputOffset + Output offset in pixel + */ + void setTarget( const IBitmapSharedPtr& rTarget, + const ::basegfx::B2ISize& rOutputOffset ); + + + // CanvasHelper functionality is implementation-inherited. yuck. + // ============================================================= + void clear(); + + css::uno::Reference< css::rendering::XCachedPrimitive > + drawTextLayout( const css::rendering::XCanvas* pCanvas, + const css::uno::Reference< + css::rendering::XTextLayout >& laidOutText, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState ); + + // BitmapCanvasHelper functionality + // ================================ + + void copyRect( const css::rendering::XCanvas* pCanvas, + const css::uno::Reference< + css::rendering::XBitmapCanvas >& sourceCanvas, + const css::geometry::RealRectangle2D& sourceRect, + const css::rendering::ViewState& sourceViewState, + const css::rendering::RenderState& sourceRenderState, + const css::geometry::RealRectangle2D& destRect, + const css::rendering::ViewState& destViewState, + const css::rendering::RenderState& destRenderState ); + + css::geometry::IntegerSize2D getSize(); + + css::uno::Reference< css::rendering::XBitmap > + getScaledBitmap( const css::geometry::RealSize2D& newSize, + bool beFast ); + + css::uno::Sequence< sal_Int8 > + getData( css::rendering::IntegerBitmapLayout& bitmapLayout, + const css::geometry::IntegerRectangle2D& rect ); + + void setData( const css::uno::Sequence< sal_Int8 >& data, + const css::rendering::IntegerBitmapLayout& bitmapLayout, + const css::geometry::IntegerRectangle2D& rect ); + + void setPixel( const css::uno::Sequence< sal_Int8 >& color, + const css::rendering::IntegerBitmapLayout& bitmapLayout, + const css::geometry::IntegerPoint2D& pos ); + + css::uno::Sequence< sal_Int8 > + getPixel( css::rendering::IntegerBitmapLayout& bitmapLayout, + const css::geometry::IntegerPoint2D& pos ); + + css::uno::Reference< css::rendering::XBitmapPalette > getPalette(); + + css::rendering::IntegerBitmapLayout getMemoryLayout(); + + bool hasAlpha() const; + + protected: + /// Render target + IBitmapSharedPtr mpTarget; + }; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_bitmapprovider.hxx b/canvas/source/directx/dx_bitmapprovider.hxx new file mode 100644 index 000000000..ad83abee0 --- /dev/null +++ b/canvas/source/directx/dx_bitmapprovider.hxx @@ -0,0 +1,34 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include "dx_ibitmap.hxx" +#include <memory> + +namespace dxcanvas +{ +struct BitmapProvider +{ + virtual ~BitmapProvider() {} + virtual IBitmapSharedPtr getBitmap() const = 0; +}; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_canvas.cxx b/canvas/source/directx/dx_canvas.cxx new file mode 100644 index 000000000..7f29813c9 --- /dev/null +++ b/canvas/source/directx/dx_canvas.cxx @@ -0,0 +1,258 @@ +// /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <memory> +#include <utility> + +#include <sal/log.hxx> + +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/numeric/ftools.hxx> +#include <basegfx/point/b2dpoint.hxx> +#include <basegfx/utils/canvastools.hxx> +#include <com/sun/star/awt/XSystemDependentWindowPeer.hpp> +#include <com/sun/star/awt/XWindow.hpp> +#include <com/sun/star/lang/NoSupportException.hpp> +#include <com/sun/star/lang/XSingleServiceFactory.hpp> +#include <com/sun/star/registry/XRegistryKey.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <cppuhelper/supportsservice.hxx> +#include <osl/mutex.hxx> +#include <tools/diagnose_ex.h> +#include <vcl/sysdata.hxx> +#include <vcl/skia/SkiaHelper.hxx> +#include <vcl/window.hxx> + +#include <canvas/canvastools.hxx> + +#include "dx_canvas.hxx" +#include "dx_graphicsprovider.hxx" +#include "dx_winstuff.hxx" + +using namespace ::com::sun::star; + +namespace dxcanvas +{ + namespace { + + /// Actual canonical implementation of the GraphicsProvider interface + class GraphicsProviderImpl : public GraphicsProvider + { + GraphicsSharedPtr mpGraphics; + public: + explicit GraphicsProviderImpl( GraphicsSharedPtr && pGraphics ) : mpGraphics( std::move(pGraphics) ) {} + virtual GraphicsSharedPtr getGraphics() override { return mpGraphics; } + }; + + } + + Canvas::Canvas( const uno::Sequence< uno::Any >& aArguments, + const uno::Reference< uno::XComponentContext >& rxContext ) : + maArguments(aArguments), + mxComponentContext( rxContext ) + { + } + + void Canvas::initialize() + { + // #i64742# Only perform initialization when not in probe mode + if( maArguments.getLength() == 0 ) + return; + + assert( !SkiaHelper::isVCLSkiaEnabled() ); + + SAL_INFO("canvas.directx", "Canvas::initialize called" ); + + // At index 1, we expect a HWND handle here, containing a + // pointer to a valid window, on which to output + // At index 2, we expect the current window bound rect + ENSURE_ARG_OR_THROW( maArguments.getLength() >= 5 && + maArguments[4].getValueTypeClass() == uno::TypeClass_SEQUENCE, + "Canvas::initialize: wrong number of arguments, or wrong types" ); + + uno::Sequence<sal_Int8> aSeq; + maArguments[4] >>= aSeq; + + const SystemGraphicsData* pSysData=reinterpret_cast<const SystemGraphicsData*>(aSeq.getConstArray()); + if( !pSysData || !pSysData->hDC ) + throw lang::NoSupportException("Passed SystemGraphicsData or HDC invalid!"); + + sal_Int64 nPtr = 0; + maArguments[0] >>= nPtr; + OutputDevice* pOutDev = reinterpret_cast<OutputDevice*>(nPtr); + ENSURE_ARG_OR_THROW( pOutDev != nullptr,"Canvas::initialize: invalid OutDev pointer" ); + + // setup helper + maDeviceHelper.init( pSysData->hDC, pOutDev, *this ); + maCanvasHelper.setDevice( *this ); + maCanvasHelper.setTarget( + std::make_shared<GraphicsProviderImpl>( + GraphicsSharedPtr(Gdiplus::Graphics::FromHDC(pSysData->hDC)))); + + maArguments.realloc(0); + } + + void Canvas::disposeThis() + { + ::osl::MutexGuard aGuard( m_aMutex ); + + mxComponentContext.clear(); + + // forward to parent + CanvasBaseT::disposeThis(); + } + + OUString SAL_CALL Canvas::getServiceName( ) + { + return "com.sun.star.rendering.Canvas.GDI+"; + } + + // XServiceInfo + css::uno::Sequence<OUString> Canvas::getSupportedServiceNames( ) + { + return { "com.sun.star.rendering.Canvas.GDI+" }; + } + OUString Canvas::getImplementationName( ) + { + return "com.sun.star.comp.rendering.Canvas.GDI+"; + } + sal_Bool Canvas::supportsService( const OUString& sServiceName ) + { + return cppu::supportsService(this, sServiceName); + } + + BitmapCanvas::BitmapCanvas( const uno::Sequence< uno::Any >& aArguments, + const uno::Reference< uno::XComponentContext >& rxContext ) : + maArguments(aArguments), + mxComponentContext( rxContext ), + mpTarget() + { + } + + void BitmapCanvas::initialize() + { + // #i64742# Only perform initialization when not in probe mode + if( maArguments.getLength() == 0 ) + return; + + SAL_INFO("canvas.directx", "BitmapCanvas::initialize called" ); + + // At index 1, we expect a HWND handle here, containing a + // pointer to a valid window, on which to output + // At index 2, we expect the current window bound rect + ENSURE_ARG_OR_THROW( maArguments.getLength() >= 5 && + maArguments[4].getValueTypeClass() == uno::TypeClass_SEQUENCE, + "Canvas::initialize: wrong number of arguments, or wrong types" ); + + uno::Sequence<sal_Int8> aSeq; + maArguments[4] >>= aSeq; + + const SystemGraphicsData* pSysData=reinterpret_cast<const SystemGraphicsData*>(aSeq.getConstArray()); + if( !pSysData || !pSysData->hDC ) + throw lang::NoSupportException( "Passed SystemGraphicsData or HDC invalid!"); + + sal_Int64 nPtr = 0; + maArguments[0] >>= nPtr; + OutputDevice* pOutDev = reinterpret_cast<OutputDevice*>(nPtr); + ENSURE_ARG_OR_THROW( pOutDev != nullptr,"Canvas::initialize: invalid OutDev pointer" ); + + // setup helper + maDeviceHelper.init( pSysData->hDC, pOutDev, *this ); + maCanvasHelper.setDevice( *this ); + + // check whether we can actually provide a BitmapCanvas + // here. for this, check whether the HDC has a bitmap + // selected. + HBITMAP hBmp; + hBmp=static_cast<HBITMAP>(GetCurrentObject(pSysData->hDC, OBJ_BITMAP)); + if( !hBmp || GetObjectType(pSysData->hDC) != OBJ_MEMDC ) + { + throw lang::NoSupportException( "Passed HDC is no mem DC/has no bitmap selected!"); + } + + mpTarget = std::make_shared<DXBitmap>( + BitmapSharedPtr( + Gdiplus::Bitmap::FromHBITMAP( + hBmp, nullptr) ), + false ); + + maCanvasHelper.setTarget( mpTarget ); + + maArguments.realloc(0); + } + + void BitmapCanvas::disposeThis() + { + ::osl::MutexGuard aGuard( m_aMutex ); + + mpTarget.reset(); + mxComponentContext.clear(); + + // forward to parent + BitmapCanvasBaseT::disposeThis(); + } + + OUString SAL_CALL BitmapCanvas::getServiceName( ) + { + return "com.sun.star.rendering.BitmapCanvas.GDI+"; + } + + // XServiceInfo + css::uno::Sequence<OUString> BitmapCanvas::getSupportedServiceNames( ) + { + return { "com.sun.star.rendering.BitmapCanvas.GDI+" }; + } + OUString BitmapCanvas::getImplementationName( ) + { + return "com.sun.star.comp.rendering.BitmapCanvas.GDI+"; + } + sal_Bool BitmapCanvas::supportsService( const OUString& sServiceName ) + { + return cppu::supportsService(this, sServiceName); + } + + IBitmapSharedPtr BitmapCanvas::getBitmap() const + { + return mpTarget; + } + + extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* + canvas_gdiplus_Canvas_get_implementation( + css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const& args) + { + rtl::Reference<Canvas> xCanvas(new Canvas(args, context)); + xCanvas->initialize(); + xCanvas->acquire(); + return static_cast<cppu::OWeakObject*>(xCanvas.get()); + } + + extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* + canvas_gdiplus_BitmapCanvas_get_implementation( + css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const& args) + { + rtl::Reference<BitmapCanvas> xCanvas(new BitmapCanvas(args, context)); + xCanvas->initialize(); + xCanvas->acquire(); + return static_cast<cppu::OWeakObject*>(xCanvas.get()); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_canvas.hxx b/canvas/source/directx/dx_canvas.hxx new file mode 100644 index 000000000..5a00e07fa --- /dev/null +++ b/canvas/source/directx/dx_canvas.hxx @@ -0,0 +1,173 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include <rtl/ref.hxx> + +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/lang/XServiceName.hpp> +#include <com/sun/star/util/XUpdatable.hpp> +#include <com/sun/star/rendering/XBitmapCanvas.hpp> +#include <com/sun/star/rendering/XIntegerBitmap.hpp> +#include <com/sun/star/rendering/XGraphicDevice.hpp> +#include <com/sun/star/rendering/XBufferController.hpp> + +#include <cppuhelper/compbase.hxx> +#include <comphelper/uno3.hxx> + +#include <base/integerbitmapbase.hxx> +#include <base/basemutexhelper.hxx> +#include <base/graphicdevicebase.hxx> +#include <base/canvasbase.hxx> +#include <base/bitmapcanvasbase.hxx> + +#include "dx_bitmapprovider.hxx" +#include "dx_canvashelper.hxx" +#include "dx_bitmapcanvashelper.hxx" +#include "dx_impltools.hxx" +#include "dx_devicehelper.hxx" + + +namespace dxcanvas +{ + typedef ::cppu::WeakComponentImplHelper< css::rendering::XCanvas, + css::rendering::XGraphicDevice, + css::lang::XMultiServiceFactory, + css::util::XUpdatable, + css::beans::XPropertySet, + css::lang::XServiceName, + css::lang::XServiceInfo> GraphicDeviceBase1_Base; + typedef ::canvas::GraphicDeviceBase< ::canvas::BaseMutexHelper< GraphicDeviceBase1_Base >, + DeviceHelper, + ::osl::MutexGuard, + ::cppu::OWeakObject > CanvasBase1_Base; + typedef ::canvas::CanvasBase< CanvasBase1_Base, + CanvasHelper, + ::osl::MutexGuard, + ::cppu::OWeakObject > CanvasBaseT; + + /** Product of this component's factory. + + The Canvas object combines the actual Window canvas with + the XGraphicDevice interface. This is because there's a + one-to-one relation between them, anyway, since each window + can have exactly one canvas and one associated + XGraphicDevice. And to avoid messing around with circular + references, this is implemented as one single object. + */ + class Canvas : public CanvasBaseT + { + public: + Canvas( const css::uno::Sequence< + css::uno::Any >& aArguments, + const css::uno::Reference< + css::uno::XComponentContext >& rxContext ); + + void initialize(); + + /// Dispose all internal references + virtual void disposeThis() override; + + // Forwarding the XComponent implementation to the + // cppu::ImplHelper templated base + // Classname Base doing refcounting Base implementing the XComponent interface + // | | | + // V V V + DECLARE_UNO3_XCOMPONENT_AGG_DEFAULTS( Canvas, GraphicDeviceBase1_Base, ::cppu::WeakComponentImplHelperBase ) + + // XServiceName + virtual OUString SAL_CALL getServiceName( ) override; + + // XServiceInfo + virtual css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames( ) override; + virtual OUString SAL_CALL getImplementationName( ) override; + virtual sal_Bool SAL_CALL supportsService( const OUString& ) override; + + private: + css::uno::Sequence< css::uno::Any > maArguments; + css::uno::Reference< css::uno::XComponentContext > mxComponentContext; + }; + + typedef ::cppu::WeakComponentImplHelper< css::rendering::XBitmapCanvas, + css::rendering::XIntegerBitmap, + css::rendering::XGraphicDevice, + css::lang::XMultiServiceFactory, + css::util::XUpdatable, + css::beans::XPropertySet, + css::lang::XServiceName, + css::lang::XServiceInfo > GraphicDeviceBase2_Base; + typedef ::canvas::GraphicDeviceBase< ::canvas::BaseMutexHelper< GraphicDeviceBase2_Base >, + DeviceHelper, + ::osl::MutexGuard, + ::cppu::OWeakObject > CanvasBase2_Base; + typedef ::canvas::IntegerBitmapBase< + canvas::BitmapCanvasBase2< + CanvasBase2_Base, + BitmapCanvasHelper, + ::osl::MutexGuard, + ::cppu::OWeakObject> > BitmapCanvasBaseT; + + /** Product of this component's factory. + + The Canvas object combines the actual Window canvas with + the XGraphicDevice interface. This is because there's a + one-to-one relation between them, anyway, since each window + can have exactly one canvas and one associated + XGraphicDevice. And to avoid messing around with circular + references, this is implemented as one single object. + */ + class BitmapCanvas : public BitmapCanvasBaseT, public BitmapProvider + { + public: + BitmapCanvas( const css::uno::Sequence< css::uno::Any >& aArguments, + const css::uno::Reference< css::uno::XComponentContext >& rxContext ); + + void initialize(); + + /// Dispose all internal references + virtual void disposeThis() override; + + // Forwarding the XComponent implementation to the + // cppu::ImplHelper templated base + // Classname Base doing refcounting Base implementing the XComponent interface + // | | | + // V V V + DECLARE_UNO3_XCOMPONENT_AGG_DEFAULTS( BitmapCanvas, GraphicDeviceBase2_Base, ::cppu::WeakComponentImplHelperBase ) + + // XServiceName + virtual OUString SAL_CALL getServiceName( ) override; + + // XServiceInfo + virtual css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames( ) override; + virtual OUString SAL_CALL getImplementationName( ) override; + virtual sal_Bool SAL_CALL supportsService( const OUString& ) override; + + // BitmapProvider + virtual IBitmapSharedPtr getBitmap() const override; + + private: + css::uno::Sequence< css::uno::Any > maArguments; + css::uno::Reference< css::uno::XComponentContext > mxComponentContext; + IBitmapSharedPtr mpTarget; + }; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_canvasbitmap.cxx b/canvas/source/directx/dx_canvasbitmap.cxx new file mode 100644 index 000000000..217d3edd2 --- /dev/null +++ b/canvas/source/directx/dx_canvasbitmap.cxx @@ -0,0 +1,256 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <memory> + +#include <cppuhelper/supportsservice.hxx> +#include <tools/diagnose_ex.h> +#include <vcl/bitmapex.hxx> + +#include <canvas/canvastools.hxx> + +#include "dx_canvasbitmap.hxx" +#include "dx_impltools.hxx" + + +using namespace ::com::sun::star; + +namespace dxcanvas +{ + CanvasBitmap::CanvasBitmap( const IBitmapSharedPtr& rBitmap, + const DeviceRef& rDevice ) : + mpDevice( rDevice ), + mpBitmap( rBitmap ) + { + ENSURE_OR_THROW( mpDevice.is() && mpBitmap, + "CanvasBitmap::CanvasBitmap(): Invalid surface or device" ); + + maCanvasHelper.setDevice( *mpDevice ); + maCanvasHelper.setTarget( mpBitmap ); + } + + void CanvasBitmap::disposeThis() + { + mpBitmap.reset(); + mpDevice.clear(); + + // forward to parent + CanvasBitmap_Base::disposeThis(); + } + + namespace { + + struct AlphaDIB + { + BITMAPINFOHEADER bmiHeader; + RGBQUAD bmiColors[256]; + AlphaDIB() + : bmiHeader({0,0,0,1,8,BI_RGB,0,0,0,0,0}) + { + for (size_t i = 0; i < 256; ++i) + { + // this here fills palette with grey level colors, starting + // from 0,0,0 up to 255,255,255 + BYTE const b(i); + bmiColors[i] = { b,b,b,b }; + } + } + }; + + } + + uno::Any SAL_CALL CanvasBitmap::getFastPropertyValue( sal_Int32 nHandle ) + { + uno::Any aRes; + // 0 ... get BitmapEx + // 1 ... get Pixbuf with bitmap RGB content + // 2 ... get Pixbuf with bitmap alpha mask + switch( nHandle ) + { + // sorry, no BitmapEx here... + case 0: + aRes <<= reinterpret_cast<sal_Int64>( nullptr ); + break; + + case 1: + { + if(!mpBitmap->hasAlpha()) + { + HBITMAP aHBmp; + mpBitmap->getBitmap()->GetHBITMAP(Gdiplus::Color(), &aHBmp ); + + uno::Sequence< uno::Any > args{ uno::Any(reinterpret_cast<sal_Int64>(aHBmp)) }; + aRes <<= args; + } + else + { + // need to copy&convert the bitmap, since dx + // canvas uses inline alpha channel + HDC hScreenDC=GetDC(nullptr); + const basegfx::B2IVector aSize(mpBitmap->getSize()); + HBITMAP hBmpBitmap = CreateCompatibleBitmap( hScreenDC, + aSize.getX(), + aSize.getY() ); + if( !hBmpBitmap ) + return aRes; + + BITMAPINFOHEADER aBIH; + + aBIH.biSize = sizeof( BITMAPINFOHEADER ); + aBIH.biWidth = aSize.getX(); + aBIH.biHeight = -aSize.getY(); + aBIH.biPlanes = 1; + aBIH.biBitCount = 32; + aBIH.biCompression = BI_RGB; // expects pixel in + // bbggrrxx format + // (little endian) + aBIH.biSizeImage = 0; + aBIH.biXPelsPerMeter = 0; + aBIH.biYPelsPerMeter = 0; + aBIH.biClrUsed = 0; + aBIH.biClrImportant = 0; + + Gdiplus::BitmapData aBmpData; + aBmpData.Width = aSize.getX(); + aBmpData.Height = aSize.getY(); + aBmpData.Stride = 4*aBmpData.Width; + aBmpData.PixelFormat = PixelFormat32bppARGB; + aBmpData.Scan0 = nullptr; + const Gdiplus::Rect aRect( 0,0,aSize.getX(),aSize.getY() ); + BitmapSharedPtr pGDIPlusBitmap=mpBitmap->getBitmap(); + if( Gdiplus::Ok != pGDIPlusBitmap->LockBits( &aRect, + Gdiplus::ImageLockModeRead, + PixelFormat32bppARGB, // outputs ARGB (big endian) + &aBmpData ) ) + { + // failed to lock, bail out + return aRes; + } + + // now aBmpData.Scan0 contains our bits - push + // them into HBITMAP, ignoring alpha + SetDIBits( hScreenDC, hBmpBitmap, 0, aSize.getY(), aBmpData.Scan0, reinterpret_cast<PBITMAPINFO>(&aBIH), DIB_RGB_COLORS ); + + pGDIPlusBitmap->UnlockBits( &aBmpData ); + + uno::Sequence< uno::Any > args{ uno::Any(reinterpret_cast<sal_Int64>(hBmpBitmap)) }; + aRes <<= args; + } + } + break; + + case 2: + { + if(!mpBitmap->hasAlpha()) + { + return aRes; + } + else + { + static AlphaDIB aDIB; + + // need to copy&convert the bitmap, since dx + // canvas uses inline alpha channel + HDC hScreenDC=GetDC(nullptr); + const basegfx::B2IVector aSize(mpBitmap->getSize()); + HBITMAP hBmpBitmap = CreateCompatibleBitmap( hScreenDC, aSize.getX(), aSize.getY() ); + if( !hBmpBitmap ) + return aRes; + + aDIB.bmiHeader.biSize = sizeof( BITMAPINFOHEADER ); + aDIB.bmiHeader.biWidth = aSize.getX(); + aDIB.bmiHeader.biHeight = -aSize.getY(); + aDIB.bmiHeader.biPlanes = 1; + aDIB.bmiHeader.biBitCount = 8; + aDIB.bmiHeader.biCompression = BI_RGB; + aDIB.bmiHeader.biSizeImage = 0; + aDIB.bmiHeader.biXPelsPerMeter = 0; + aDIB.bmiHeader.biYPelsPerMeter = 0; + aDIB.bmiHeader.biClrUsed = 0; + aDIB.bmiHeader.biClrImportant = 0; + + Gdiplus::BitmapData aBmpData; + aBmpData.Width = aSize.getX(); + aBmpData.Height = aSize.getY(); + aBmpData.Stride = 4*aBmpData.Width; + aBmpData.PixelFormat = PixelFormat32bppARGB; + aBmpData.Scan0 = nullptr; + const Gdiplus::Rect aRect( 0,0,aSize.getX(),aSize.getY() ); + BitmapSharedPtr pGDIPlusBitmap=mpBitmap->getBitmap(); + if( Gdiplus::Ok != pGDIPlusBitmap->LockBits( &aRect, + Gdiplus::ImageLockModeRead, + PixelFormat32bppARGB, // outputs ARGB (big endian) + &aBmpData ) ) + { + // failed to lock, bail out + return aRes; + } + + // copy only alpha channel to pAlphaBits + const sal_Int32 nScanWidth((aSize.getX() + 3) & ~3); + std::unique_ptr<sal_uInt8[]> pAlphaBits( new sal_uInt8[nScanWidth*aSize.getY()] ); + const sal_uInt8* pInBits=static_cast<sal_uInt8*>(aBmpData.Scan0); + pInBits+=3; + for( sal_Int32 y=0; y<aSize.getY(); ++y ) + { + sal_uInt8* pOutBits=pAlphaBits.get()+y*nScanWidth; + for( sal_Int32 x=0; x<aSize.getX(); ++x ) + { + *pOutBits++ = 255-*pInBits; + pInBits += 4; + } + } + + pGDIPlusBitmap->UnlockBits( &aBmpData ); + + // set bits to newly create HBITMAP + SetDIBits( hScreenDC, hBmpBitmap, 0, + aSize.getY(), pAlphaBits.get(), + reinterpret_cast<PBITMAPINFO>(&aDIB), DIB_RGB_COLORS ); + + uno::Sequence< uno::Any > args{ uno::Any(reinterpret_cast<sal_Int64>(hBmpBitmap)) }; + aRes <<= args; + } + } + break; + } + + return aRes; + } + + OUString SAL_CALL CanvasBitmap::getImplementationName( ) + { + return "DXCanvas.CanvasBitmap"; + } + + sal_Bool SAL_CALL CanvasBitmap::supportsService( const OUString& ServiceName ) + { + return cppu::supportsService( this, ServiceName ); + } + + uno::Sequence< OUString > SAL_CALL CanvasBitmap::getSupportedServiceNames( ) + { + return { "com.sun.star.rendering.CanvasBitmap" }; + } + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_canvasbitmap.hxx b/canvas/source/directx/dx_canvasbitmap.hxx new file mode 100644 index 000000000..b1725b447 --- /dev/null +++ b/canvas/source/directx/dx_canvasbitmap.hxx @@ -0,0 +1,91 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/rendering/XBitmapCanvas.hpp> +#include <com/sun/star/rendering/XIntegerBitmap.hpp> +#include <com/sun/star/beans/XFastPropertySet.hpp> +#include <basegfx/vector/b2isize.hxx> +#include <cppuhelper/compbase.hxx> +#include <base/basemutexhelper.hxx> +#include <base/bitmapcanvasbase.hxx> +#include <base/integerbitmapbase.hxx> + +#include "dx_bitmapprovider.hxx" +#include "dx_bitmapcanvashelper.hxx" +#include "dx_devicehelper.hxx" +#include "dx_impltools.hxx" +#include "dx_ibitmap.hxx" + + +/* Definition of CanvasBitmap class */ + +namespace dxcanvas +{ + typedef ::cppu::WeakComponentImplHelper< css::rendering::XBitmapCanvas, + css::rendering::XIntegerBitmap, + css::lang::XServiceInfo, + css::beans::XFastPropertySet > CanvasBitmapBase_Base; + typedef ::canvas::IntegerBitmapBase< + canvas::BitmapCanvasBase2< + ::canvas::BaseMutexHelper< CanvasBitmapBase_Base >, + BitmapCanvasHelper, + ::osl::MutexGuard, + ::cppu::OWeakObject> > CanvasBitmap_Base; + + class CanvasBitmap : public CanvasBitmap_Base, public BitmapProvider + { + public: + /** Create a canvas bitmap for the given surface + + @param rSurface + Surface to create API object for. + + @param rDevice + Reference device, with which bitmap should be compatible + */ + CanvasBitmap( const IBitmapSharedPtr& rSurface, + const DeviceRef& rDevice ); + + /// Dispose all internal references + virtual void disposeThis() override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName( ) override; + virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames( ) override; + + // BitmapProvider + virtual IBitmapSharedPtr getBitmap() const override { return mpBitmap; } + + virtual css::uno::Any SAL_CALL getFastPropertyValue(sal_Int32 nHandle) override; + virtual void SAL_CALL setFastPropertyValue(sal_Int32, const css::uno::Any&) override {} + + private: + /** MUST hold here, too, since CanvasHelper only contains a + raw pointer (without refcounting) + */ + DeviceRef mpDevice; + IBitmapSharedPtr mpBitmap; + }; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_canvascustomsprite.cxx b/canvas/source/directx/dx_canvascustomsprite.cxx new file mode 100644 index 000000000..a0e58f29d --- /dev/null +++ b/canvas/source/directx/dx_canvascustomsprite.cxx @@ -0,0 +1,106 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <memory> + +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/point/b2dpoint.hxx> +#include <cppuhelper/supportsservice.hxx> +#include <rtl/math.hxx> +#include <tools/diagnose_ex.h> + +#include <canvas/canvastools.hxx> + +#include "dx_canvascustomsprite.hxx" +#include "dx_impltools.hxx" +#include "dx_spritecanvas.hxx" + +using namespace ::com::sun::star; + +namespace dxcanvas +{ + CanvasCustomSprite::CanvasCustomSprite( const css::geometry::RealSize2D& rSpriteSize, + const SpriteCanvasRef& rRefDevice, + const IDXRenderModuleSharedPtr& rRenderModule, + const std::shared_ptr<canvas::ISurfaceProxyManager>& rSurfaceProxy, + bool bShowSpriteBounds ) : + mpSpriteCanvas( rRefDevice ), + mpSurface() + { + ENSURE_OR_THROW( rRefDevice, + "CanvasCustomSprite::CanvasCustomSprite(): Invalid sprite canvas" ); + + mpSurface = std::make_shared<DXSurfaceBitmap>( + ::basegfx::B2IVector( + ::canvas::tools::roundUp( rSpriteSize.Width ), + ::canvas::tools::roundUp( rSpriteSize.Height )), + rSurfaceProxy, + rRenderModule, + true); + + maCanvasHelper.setDevice( *rRefDevice ); + maCanvasHelper.setTarget( mpSurface ); + + maSpriteHelper.init( rSpriteSize, + rRefDevice, + rRenderModule, + mpSurface, + bShowSpriteBounds ); + + // clear sprite to 100% transparent + maCanvasHelper.clear(); + } + + void CanvasCustomSprite::disposeThis() + { + ::osl::MutexGuard aGuard( m_aMutex ); + + mpSurface.reset(); + mpSpriteCanvas.clear(); + + // forward to parent + CanvasCustomSpriteBaseT::disposeThis(); + } + + OUString SAL_CALL CanvasCustomSprite::getImplementationName() + { + return "DXCanvas.CanvasCustomSprite"; + } + + sal_Bool SAL_CALL CanvasCustomSprite::supportsService( const OUString& ServiceName ) + { + return cppu::supportsService( this, ServiceName ); + } + + uno::Sequence< OUString > SAL_CALL CanvasCustomSprite::getSupportedServiceNames() + { + return { "com.sun.star.rendering.CanvasCustomSprite" }; + } + + void CanvasCustomSprite::redraw() const + { + ::osl::MutexGuard aGuard( m_aMutex ); + + maSpriteHelper.redraw( mbSurfaceDirty ); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_canvascustomsprite.hxx b/canvas/source/directx/dx_canvascustomsprite.hxx new file mode 100644 index 000000000..2411a1527 --- /dev/null +++ b/canvas/source/directx/dx_canvascustomsprite.hxx @@ -0,0 +1,130 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include <cppuhelper/compbase.hxx> +#include <comphelper/uno3.hxx> + +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/lang/XComponent.hpp> +#include <com/sun/star/rendering/XCustomSprite.hpp> +#include <com/sun/star/rendering/XIntegerBitmap.hpp> +#include <com/sun/star/rendering/XPolyPolygon2D.hpp> + +#include <basegfx/point/b2dpoint.hxx> +#include <basegfx/vector/b2isize.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> + +#include <base/basemutexhelper.hxx> +#include <base/canvascustomspritebase.hxx> + +#include "dx_sprite.hxx" +#include "dx_surfacebitmap.hxx" +#include "dx_bitmapcanvashelper.hxx" +#include "dx_spritehelper.hxx" +#include "dx_spritecanvas.hxx" + + +namespace dxcanvas +{ + typedef ::cppu::WeakComponentImplHelper< css::rendering::XCustomSprite, + css::rendering::XBitmapCanvas, + css::rendering::XIntegerBitmap, + css::lang::XServiceInfo > CanvasCustomSpriteBase_Base; + /** Mixin Sprite + + Have to mixin the Sprite interface before deriving from + ::canvas::CanvasCustomSpriteBase, as this template should + already implement some of those interface methods. + + The reason why this appears kinda convoluted is the fact that + we cannot specify non-IDL types as WeakComponentImplHelper + template args, and furthermore, don't want to derive + ::canvas::CanvasCustomSpriteBase directly from + ::canvas::Sprite (because derivees of + ::canvas::CanvasCustomSpriteBase have to explicitly forward + the XInterface methods (e.g. via DECLARE_UNO3_AGG_DEFAULTS) + anyway). Basically, ::canvas::CanvasCustomSpriteBase should + remain a base class that provides implementation, not to + enforce any specific interface on its derivees. + */ + class CanvasCustomSpriteSpriteBase_Base : public ::canvas::BaseMutexHelper< CanvasCustomSpriteBase_Base >, + public Sprite + { + }; + + typedef ::canvas::CanvasCustomSpriteBase< CanvasCustomSpriteSpriteBase_Base, + SpriteHelper, + BitmapCanvasHelper, + ::osl::MutexGuard, + ::cppu::OWeakObject > CanvasCustomSpriteBaseT; + + /* Definition of CanvasCustomSprite class */ + + class CanvasCustomSprite : public CanvasCustomSpriteBaseT + { + public: + /** Create a custom sprite + + @param rSpriteSize + Size of the sprite in pixel + + @param rRefDevice + Associated output device + + @param rSpriteCanvas + Target canvas + + @param rDevice + Target DX device + */ + CanvasCustomSprite( const css::geometry::RealSize2D& rSpriteSize, + const SpriteCanvasRef& rRefDevice, + const IDXRenderModuleSharedPtr& rRenderModule, + const std::shared_ptr<canvas::ISurfaceProxyManager>& rSurfaceProxy, + bool bShowSpriteBounds ); + + virtual void disposeThis() override; + + // Forwarding the XComponent implementation to the + // cppu::ImplHelper templated base + // Classname Base doing refcount Base implementing the XComponent interface + // | | | + // V V V + DECLARE_UNO3_XCOMPONENT_AGG_DEFAULTS( CanvasCustomSprite, CanvasCustomSpriteBase_Base, ::cppu::WeakComponentImplHelperBase ) + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override; + + // Sprite + virtual void redraw() const override; + + private: + /** MUST hold here, too, since BitmapCanvasHelper only contains a + raw pointer (without refcounting) + */ + SpriteCanvasRef mpSpriteCanvas; + DXSurfaceBitmapSharedPtr mpSurface; + }; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_canvasfont.cxx b/canvas/source/directx/dx_canvasfont.cxx new file mode 100644 index 000000000..aeb307052 --- /dev/null +++ b/canvas/source/directx/dx_canvasfont.cxx @@ -0,0 +1,162 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <memory> + +#include <o3tl/char16_t2wchar_t.hxx> + +#include <com/sun/star/rendering/PanoseWeight.hpp> +#include <com/sun/star/rendering/XSpriteCanvas.hpp> +#include <cppuhelper/supportsservice.hxx> + +#include "dx_canvasfont.hxx" +#include "dx_spritecanvas.hxx" +#include "dx_textlayout.hxx" +#include "dx_winstuff.hxx" + +using namespace ::com::sun::star; + +namespace dxcanvas +{ + namespace + { + INT calcFontStyle( const rendering::FontRequest& rFontRequest ) + { + INT nFontStyle( Gdiplus::FontStyleRegular ); + + if( rFontRequest.FontDescription.FontDescription.Weight > rendering::PanoseWeight::BOOK ) + nFontStyle = Gdiplus::FontStyleBold; + + return nFontStyle; + } + } + + CanvasFont::CanvasFont( const rendering::FontRequest& rFontRequest, + const uno::Sequence< beans::PropertyValue >& extraFontProperties, + const geometry::Matrix2D& fontMatrix ) : + CanvasFont_Base( m_aMutex ), + mpGdiPlusUser( GDIPlusUser::createInstance() ), + mpFontFamily(), + mpFont(), + maFontRequest( rFontRequest ), + mnEmphasisMark(0), + maFontMatrix( fontMatrix ) + { + mpFontFamily = std::make_shared<Gdiplus::FontFamily>(o3tl::toW(rFontRequest.FontDescription.FamilyName.getStr()),nullptr); + if( !mpFontFamily->IsAvailable() ) + mpFontFamily = std::make_shared<Gdiplus::FontFamily>(L"Arial",nullptr); + + mpFont = std::make_shared<Gdiplus::Font>( mpFontFamily.get(), + static_cast<Gdiplus::REAL>(rFontRequest.CellSize), + calcFontStyle( rFontRequest ), + Gdiplus::UnitWorld ); + + ::canvas::tools::extractExtraFontProperties(extraFontProperties, mnEmphasisMark); + } + + void SAL_CALL CanvasFont::disposing() + { + ::osl::MutexGuard aGuard( m_aMutex ); + + mpFont.reset(); + mpFontFamily.reset(); + mpGdiPlusUser.reset(); + } + + uno::Reference< rendering::XTextLayout > SAL_CALL CanvasFont::createTextLayout( const rendering::StringContext& aText, + sal_Int8 nDirection, + sal_Int64 nRandomSeed ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + + return new TextLayout( aText, nDirection, nRandomSeed, ImplRef( this ) ); + } + + uno::Sequence< double > SAL_CALL CanvasFont::getAvailableSizes( ) + { + // TODO + return uno::Sequence< double >(); + } + + uno::Sequence< beans::PropertyValue > SAL_CALL CanvasFont::getExtraFontProperties( ) + { + // TODO + return uno::Sequence< beans::PropertyValue >(); + } + + rendering::FontRequest SAL_CALL CanvasFont::getFontRequest( ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + + return maFontRequest; + } + + rendering::FontMetrics SAL_CALL CanvasFont::getFontMetrics( ) + { + // TODO + return rendering::FontMetrics(); + } + + OUString SAL_CALL CanvasFont::getImplementationName() + { + return "DXCanvas::CanvasFont"; + } + + sal_Bool SAL_CALL CanvasFont::supportsService( const OUString& ServiceName ) + { + return cppu::supportsService( this, ServiceName ); + } + + uno::Sequence< OUString > SAL_CALL CanvasFont::getSupportedServiceNames() + { + return { "com.sun.star.rendering.CanvasFont" }; + } + + double CanvasFont::getCellAscent() const + { + ::osl::MutexGuard aGuard( m_aMutex ); + + return mpFontFamily->GetCellAscent(0); // TODO(F1): rFontRequest.styleName + } + + double CanvasFont::getEmHeight() const + { + ::osl::MutexGuard aGuard( m_aMutex ); + + return mpFontFamily->GetEmHeight(0); // TODO(F1): rFontRequest.styleName + } + + FontSharedPtr CanvasFont::getFont() const + { + ::osl::MutexGuard aGuard( m_aMutex ); + + return mpFont; + } + + const css::geometry::Matrix2D& CanvasFont::getFontMatrix() const + { + ::osl::MutexGuard aGuard( m_aMutex ); + + return maFontMatrix; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_canvasfont.hxx b/canvas/source/directx/dx_canvasfont.hxx new file mode 100644 index 000000000..c5f369d00 --- /dev/null +++ b/canvas/source/directx/dx_canvasfont.hxx @@ -0,0 +1,92 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include <cppuhelper/compbase.hxx> +#include <cppuhelper/basemutex.hxx> + +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/rendering/XCanvas.hpp> +#include <com/sun/star/rendering/XCanvasFont.hpp> + +#include <rtl/ref.hxx> + +#include <memory> + +#include "dx_winstuff.hxx" +#include "dx_gdiplususer.hxx" + + +/* Definition of CanvasFont class */ + +namespace dxcanvas +{ + typedef std::shared_ptr< Gdiplus::Font > FontSharedPtr; + typedef std::shared_ptr< Gdiplus::FontFamily > FontFamilySharedPtr; + + typedef ::cppu::WeakComponentImplHelper< css::rendering::XCanvasFont, + css::lang::XServiceInfo > CanvasFont_Base; + + class CanvasFont : public ::cppu::BaseMutex, + public CanvasFont_Base + { + public: + typedef rtl::Reference<CanvasFont> ImplRef; + /// make noncopyable + CanvasFont(const CanvasFont&) = delete; + const CanvasFont& operator=(const CanvasFont&) = delete; + + CanvasFont( const css::rendering::FontRequest& fontRequest, + const css::uno::Sequence< css::beans::PropertyValue >& extraFontProperties, + const css::geometry::Matrix2D& fontMatrix ); + + /// Dispose all internal references + virtual void SAL_CALL disposing() override; + + // XCanvasFont + virtual css::uno::Reference< css::rendering::XTextLayout > SAL_CALL createTextLayout( const css::rendering::StringContext& aText, sal_Int8 nDirection, sal_Int64 nRandomSeed ) override; + virtual css::rendering::FontRequest SAL_CALL getFontRequest( ) override; + virtual css::rendering::FontMetrics SAL_CALL getFontMetrics( ) override; + virtual css::uno::Sequence< double > SAL_CALL getAvailableSizes( ) override; + virtual css::uno::Sequence< css::beans::PropertyValue > SAL_CALL getExtraFontProperties( ) override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override; + + double getCellAscent() const; + double getEmHeight() const; + FontSharedPtr getFont() const; + const css::geometry::Matrix2D& getFontMatrix() const; + sal_uInt32 getEmphasisMark() const { return mnEmphasisMark; } + + private: + GDIPlusUserSharedPtr mpGdiPlusUser; + FontFamilySharedPtr mpFontFamily; + FontSharedPtr mpFont; + css::rendering::FontRequest maFontRequest; + sal_uInt32 mnEmphasisMark; + css::geometry::Matrix2D maFontMatrix; + }; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_canvashelper.cxx b/canvas/source/directx/dx_canvashelper.cxx new file mode 100644 index 000000000..2ca2e09e2 --- /dev/null +++ b/canvas/source/directx/dx_canvashelper.cxx @@ -0,0 +1,814 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> +#include <sal/log.hxx> + +#include <algorithm> + +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <basegfx/point/b2dpoint.hxx> +#include <basegfx/utils/canvastools.hxx> +#include <com/sun/star/rendering/CompositeOperation.hpp> +#include <com/sun/star/rendering/PathCapType.hpp> +#include <com/sun/star/rendering/PathJoinType.hpp> +#include <com/sun/star/rendering/RepaintResult.hpp> +#include <com/sun/star/rendering/TexturingMode.hpp> +#include <comphelper/sequence.hxx> +#include <o3tl/char16_t2wchar_t.hxx> +#include <rtl/math.hxx> +#include <tools/diagnose_ex.h> + +#include <canvas/canvastools.hxx> + +#include "dx_canvasfont.hxx" +#include "dx_canvashelper.hxx" +#include "dx_impltools.hxx" +#include "dx_spritecanvas.hxx" +#include "dx_textlayout.hxx" +#include "dx_vcltools.hxx" + +using namespace ::com::sun::star; + +namespace dxcanvas +{ + namespace + { + Gdiplus::LineCap gdiLineCapFromCap( sal_Int8 nCapType ) + { + switch( nCapType ) + { + case rendering::PathCapType::BUTT: + return Gdiplus::LineCapFlat; + + case rendering::PathCapType::ROUND: + return Gdiplus::LineCapRound; + + case rendering::PathCapType::SQUARE: + return Gdiplus::LineCapSquare; + + default: + ENSURE_OR_THROW( false, + "gdiLineCapFromCap(): Unexpected cap type" ); + } + + return Gdiplus::LineCapFlat; + } + + Gdiplus::DashCap gdiDashCapFromCap( sal_Int8 nCapType ) + { + switch( nCapType ) + { + case rendering::PathCapType::BUTT: + return Gdiplus::DashCapFlat; + + case rendering::PathCapType::ROUND: + return Gdiplus::DashCapRound; + + // Gdiplus does not know square, using flat would make short + // dashes disappear, so use triangle as the closest one. + case rendering::PathCapType::SQUARE: + return Gdiplus::DashCapTriangle; + + default: + ENSURE_OR_THROW( false, + "gdiDashCapFromCap(): Unexpected cap type" ); + } + + return Gdiplus::DashCapFlat; + } + + Gdiplus::LineJoin gdiJoinFromJoin( sal_Int8 nJoinType ) + { + switch( nJoinType ) + { + case rendering::PathJoinType::NONE: + SAL_WARN( "canvas.directx", "gdiJoinFromJoin(): Join NONE not possible, mapping to BEVEL (closest to NONE)" ); + return Gdiplus::LineJoinBevel; + + case rendering::PathJoinType::MITER: + // in GDI+ fallback to Bevel, if miter limit is exceeded, is not done + // by Gdiplus::LineJoinMiter but by Gdiplus::LineJoinMiterClipped + return Gdiplus::LineJoinMiterClipped; + + case rendering::PathJoinType::ROUND: + return Gdiplus::LineJoinRound; + + case rendering::PathJoinType::BEVEL: + return Gdiplus::LineJoinBevel; + + default: + ENSURE_OR_THROW( false, + "gdiJoinFromJoin(): Unexpected join type" ); + } + + return Gdiplus::LineJoinMiter; + } + } + + CanvasHelper::CanvasHelper() : + mpGdiPlusUser( GDIPlusUser::createInstance() ), + mpDevice( nullptr ), + mpGraphicsProvider(), + maOutputOffset() + { + } + + void CanvasHelper::disposing() + { + mpGraphicsProvider.reset(); + mpDevice = nullptr; + mpGdiPlusUser.reset(); + } + + void CanvasHelper::setDevice( rendering::XGraphicDevice& rDevice ) + { + mpDevice = &rDevice; + } + + void CanvasHelper::setTarget( const GraphicsProviderSharedPtr& rTarget ) + { + ENSURE_OR_THROW( rTarget, + "CanvasHelper::setTarget(): Invalid target" ); + ENSURE_OR_THROW( !mpGraphicsProvider, + "CanvasHelper::setTarget(): target set, old target would be overwritten" ); + + mpGraphicsProvider = rTarget; + } + + void CanvasHelper::setTarget( const GraphicsProviderSharedPtr& rTarget, + const ::basegfx::B2ISize& rOutputOffset ) + { + ENSURE_OR_THROW( rTarget, + "CanvasHelper::setTarget(): invalid target" ); + ENSURE_OR_THROW( !mpGraphicsProvider, + "CanvasHelper::setTarget(): target set, old target would be overwritten" ); + + mpGraphicsProvider = rTarget; + maOutputOffset = rOutputOffset; + } + + void CanvasHelper::clear() + { + if( needOutput() ) + { + GraphicsSharedPtr pGraphics( mpGraphicsProvider->getGraphics() ); + Gdiplus::Color aClearColor{Gdiplus::ARGB(Gdiplus::Color::White)}; + + ENSURE_OR_THROW( + Gdiplus::Ok == pGraphics->SetCompositingMode( + Gdiplus::CompositingModeSourceCopy ), // force set, don't blend + "CanvasHelper::clear(): GDI+ SetCompositingMode call failed" ); + ENSURE_OR_THROW( + Gdiplus::Ok == pGraphics->Clear( aClearColor ), + "CanvasHelper::clear(): GDI+ Clear call failed" ); + } + } + + void CanvasHelper::drawPoint( const rendering::XCanvas* /*pCanvas*/, + const geometry::RealPoint2D& aPoint, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState ) + { + if( needOutput() ) + { + GraphicsSharedPtr pGraphics( mpGraphicsProvider->getGraphics() ); + + setupGraphicsState( pGraphics, viewState, renderState ); + + Gdiplus::SolidBrush aBrush( + Gdiplus::Color( + tools::sequenceToArgb(renderState.DeviceColor)) ); + + // determine size of one-by-one device pixel ellipse + Gdiplus::Matrix aMatrix; + pGraphics->GetTransform(&aMatrix); + aMatrix.Invert(); + Gdiplus::PointF vector(1, 1); + aMatrix.TransformVectors(&vector); + + // paint a one-by-one circle, with the given point + // in the middle (rounded to float) + ENSURE_OR_THROW( + Gdiplus::Ok == pGraphics->FillEllipse( &aBrush, + // disambiguate call + Gdiplus::REAL(aPoint.X), + Gdiplus::REAL(aPoint.Y), + Gdiplus::REAL(vector.X), + Gdiplus::REAL(vector.Y) ), + "CanvasHelper::drawPoint(): GDI+ call failed" ); + } + } + + void CanvasHelper::drawLine( const rendering::XCanvas* /*pCanvas*/, + const geometry::RealPoint2D& aStartPoint, + const geometry::RealPoint2D& aEndPoint, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState ) + { + if( needOutput() ) + { + GraphicsSharedPtr pGraphics( mpGraphicsProvider->getGraphics() ); + + setupGraphicsState( pGraphics, viewState, renderState ); + + Gdiplus::Pen aPen( + Gdiplus::Color( + tools::sequenceToArgb(renderState.DeviceColor)), + Gdiplus::REAL(0.0) ); + + // #122683# Switched precedence of pixel offset + // mode. Seemingly, polygon stroking needs + // PixelOffsetModeNone to achieve visually pleasing + // results, whereas all other operations (e.g. polygon + // fills, bitmaps) look better with PixelOffsetModeHalf. + const Gdiplus::PixelOffsetMode aOldMode( + pGraphics->GetPixelOffsetMode() ); + pGraphics->SetPixelOffsetMode( Gdiplus::PixelOffsetModeNone ); + + Gdiplus::Status hr = pGraphics->DrawLine( &aPen, + Gdiplus::REAL(aStartPoint.X), // disambiguate call + Gdiplus::REAL(aStartPoint.Y), + Gdiplus::REAL(aEndPoint.X), + Gdiplus::REAL(aEndPoint.Y) ); + pGraphics->SetPixelOffsetMode( aOldMode ); + + ENSURE_OR_THROW( + Gdiplus::Ok == hr, + "CanvasHelper::drawLine(): GDI+ call failed" ); + } + } + + void CanvasHelper::drawBezier( const rendering::XCanvas* /*pCanvas*/, + const geometry::RealBezierSegment2D& aBezierSegment, + const geometry::RealPoint2D& aEndPoint, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState ) + { + if( needOutput() ) + { + GraphicsSharedPtr pGraphics( mpGraphicsProvider->getGraphics() ); + + setupGraphicsState( pGraphics, viewState, renderState ); + + Gdiplus::Pen aPen( + Gdiplus::Color( + tools::sequenceToArgb(renderState.DeviceColor)), + Gdiplus::REAL(0.0) ); + + // #122683# Switched precedence of pixel offset + // mode. Seemingly, polygon stroking needs + // PixelOffsetModeNone to achieve visually pleasing + // results, whereas all other operations (e.g. polygon + // fills, bitmaps) look better with PixelOffsetModeHalf. + const Gdiplus::PixelOffsetMode aOldMode( + pGraphics->GetPixelOffsetMode() ); + pGraphics->SetPixelOffsetMode( Gdiplus::PixelOffsetModeNone ); + + Gdiplus::Status hr = pGraphics->DrawBezier( &aPen, + Gdiplus::REAL(aBezierSegment.Px), // disambiguate call + Gdiplus::REAL(aBezierSegment.Py), + Gdiplus::REAL(aBezierSegment.C1x), + Gdiplus::REAL(aBezierSegment.C1y), + Gdiplus::REAL(aEndPoint.X), + Gdiplus::REAL(aEndPoint.Y), + Gdiplus::REAL(aBezierSegment.C2x), + Gdiplus::REAL(aBezierSegment.C2y) ); + + pGraphics->SetPixelOffsetMode( aOldMode ); + + ENSURE_OR_THROW( + Gdiplus::Ok == hr, + "CanvasHelper::drawBezier(): GDI+ call failed" ); + } + } + + uno::Reference< rendering::XCachedPrimitive > CanvasHelper::drawPolyPolygon( const rendering::XCanvas* /*pCanvas*/, + const uno::Reference< rendering::XPolyPolygon2D >& xPolyPolygon, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState ) + { + ENSURE_OR_THROW( xPolyPolygon.is(), + "CanvasHelper::drawPolyPolygon: polygon is NULL"); + + if( needOutput() ) + { + GraphicsSharedPtr pGraphics( mpGraphicsProvider->getGraphics() ); + + setupGraphicsState( pGraphics, viewState, renderState ); + + Gdiplus::Pen aPen( + Gdiplus::Color( + tools::sequenceToArgb(renderState.DeviceColor)), + Gdiplus::REAL(0.0) ); + + // #122683# Switched precedence of pixel offset + // mode. Seemingly, polygon stroking needs + // PixelOffsetModeNone to achieve visually pleasing + // results, whereas all other operations (e.g. polygon + // fills, bitmaps) look better with PixelOffsetModeHalf. + const Gdiplus::PixelOffsetMode aOldMode( + pGraphics->GetPixelOffsetMode() ); + pGraphics->SetPixelOffsetMode( Gdiplus::PixelOffsetModeNone ); + + GraphicsPathSharedPtr pPath( tools::graphicsPathFromXPolyPolygon2D( xPolyPolygon ) ); + + // TODO(E1): Return value + Gdiplus::Status hr = pGraphics->DrawPath( &aPen, pPath.get() ); + + pGraphics->SetPixelOffsetMode( aOldMode ); + + ENSURE_OR_THROW( + Gdiplus::Ok == hr, + "CanvasHelper::drawPolyPolygon(): GDI+ call failed" ); + } + + // TODO(P1): Provide caching here. + return uno::Reference< rendering::XCachedPrimitive >(nullptr); + } + + uno::Reference< rendering::XCachedPrimitive > CanvasHelper::strokePolyPolygon( const rendering::XCanvas* /*pCanvas*/, + const uno::Reference< rendering::XPolyPolygon2D >& xPolyPolygon, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState, + const rendering::StrokeAttributes& strokeAttributes ) + { + ENSURE_OR_THROW( xPolyPolygon.is(), + "CanvasHelper::drawPolyPolygon: polygon is NULL"); + + if( needOutput() ) + { + GraphicsSharedPtr pGraphics( mpGraphicsProvider->getGraphics() ); + + setupGraphicsState( pGraphics, viewState, renderState ); + + + // Setup stroke pen + + + Gdiplus::Pen aPen( + Gdiplus::Color( + tools::sequenceToArgb(renderState.DeviceColor)), + static_cast< Gdiplus::REAL >(strokeAttributes.StrokeWidth) ); + + // #122683# Switched precedence of pixel offset + // mode. Seemingly, polygon stroking needs + // PixelOffsetModeNone to achieve visually pleasing + // results, whereas all other operations (e.g. polygon + // fills, bitmaps) look better with PixelOffsetModeHalf. + const Gdiplus::PixelOffsetMode aOldMode( + pGraphics->GetPixelOffsetMode() ); + pGraphics->SetPixelOffsetMode( Gdiplus::PixelOffsetModeNone ); + + const bool bIsMiter(rendering::PathJoinType::MITER == strokeAttributes.JoinType); + const bool bIsNone(rendering::PathJoinType::NONE == strokeAttributes.JoinType); + + if(bIsMiter) + aPen.SetMiterLimit( static_cast< Gdiplus::REAL >(strokeAttributes.MiterLimit) ); + + const std::vector< Gdiplus::REAL >& rDashArray( + ::comphelper::sequenceToContainer< std::vector< Gdiplus::REAL >, double >( + strokeAttributes.DashArray ) ); + if( !rDashArray.empty() ) + { + aPen.SetDashPattern( rDashArray.data(), + rDashArray.size() ); + } + aPen.SetLineCap( gdiLineCapFromCap(strokeAttributes.StartCapType), + gdiLineCapFromCap(strokeAttributes.EndCapType), + gdiDashCapFromCap(strokeAttributes.StartCapType)); + if(!bIsNone) + aPen.SetLineJoin( gdiJoinFromJoin(strokeAttributes.JoinType) ); + + GraphicsPathSharedPtr pPath( tools::graphicsPathFromXPolyPolygon2D( xPolyPolygon, bIsNone ) ); + + // TODO(E1): Return value + Gdiplus::Status hr = pGraphics->DrawPath( &aPen, pPath.get() ); + + pGraphics->SetPixelOffsetMode( aOldMode ); + + ENSURE_OR_THROW( + Gdiplus::Ok == hr, + "CanvasHelper::strokePolyPolygon(): GDI+ call failed" ); + } + + // TODO(P1): Provide caching here. + return uno::Reference< rendering::XCachedPrimitive >(nullptr); + } + + uno::Reference< rendering::XCachedPrimitive > CanvasHelper::strokeTexturedPolyPolygon( const rendering::XCanvas* /*pCanvas*/, + const uno::Reference< rendering::XPolyPolygon2D >& /*xPolyPolygon*/, + const rendering::ViewState& /*viewState*/, + const rendering::RenderState& /*renderState*/, + const uno::Sequence< rendering::Texture >& /*textures*/, + const rendering::StrokeAttributes& /*strokeAttributes*/ ) + { + // TODO + return uno::Reference< rendering::XCachedPrimitive >(nullptr); + } + + uno::Reference< rendering::XCachedPrimitive > CanvasHelper::strokeTextureMappedPolyPolygon( const rendering::XCanvas* /*pCanvas*/, + const uno::Reference< rendering::XPolyPolygon2D >& /*xPolyPolygon*/, + const rendering::ViewState& /*viewState*/, + const rendering::RenderState& /*renderState*/, + const uno::Sequence< rendering::Texture >& /*textures*/, + const uno::Reference< geometry::XMapping2D >& /*xMapping*/, + const rendering::StrokeAttributes& /*strokeAttributes*/ ) + { + // TODO + return uno::Reference< rendering::XCachedPrimitive >(nullptr); + } + + uno::Reference< rendering::XPolyPolygon2D > CanvasHelper::queryStrokeShapes( const rendering::XCanvas* /*pCanvas*/, + const uno::Reference< rendering::XPolyPolygon2D >& /*xPolyPolygon*/, + const rendering::ViewState& /*viewState*/, + const rendering::RenderState& /*renderState*/, + const rendering::StrokeAttributes& /*strokeAttributes*/ ) + { + // TODO + return uno::Reference< rendering::XPolyPolygon2D >(nullptr); + } + + uno::Reference< rendering::XCachedPrimitive > CanvasHelper::fillPolyPolygon( const rendering::XCanvas* /*pCanvas*/, + const uno::Reference< rendering::XPolyPolygon2D >& xPolyPolygon, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState ) + { + ENSURE_OR_THROW( xPolyPolygon.is(), + "CanvasHelper::fillPolyPolygon: polygon is NULL"); + + if( needOutput() ) + { + GraphicsSharedPtr pGraphics( mpGraphicsProvider->getGraphics() ); + + setupGraphicsState( pGraphics, viewState, renderState ); + + Gdiplus::SolidBrush aBrush( + tools::sequenceToArgb(renderState.DeviceColor)); + + GraphicsPathSharedPtr pPath( tools::graphicsPathFromXPolyPolygon2D( xPolyPolygon ) ); + + // TODO(F1): FillRule + ENSURE_OR_THROW( Gdiplus::Ok == pGraphics->FillPath( &aBrush, pPath.get() ), + "CanvasHelper::fillPolyPolygon(): GDI+ call failed " ); + } + + // TODO(P1): Provide caching here. + return uno::Reference< rendering::XCachedPrimitive >(nullptr); + } + + uno::Reference< rendering::XCachedPrimitive > CanvasHelper::fillTextureMappedPolyPolygon( const rendering::XCanvas* /*pCanvas*/, + const uno::Reference< rendering::XPolyPolygon2D >& /*xPolyPolygon*/, + const rendering::ViewState& /*viewState*/, + const rendering::RenderState& /*renderState*/, + const uno::Sequence< rendering::Texture >& /*textures*/, + const uno::Reference< geometry::XMapping2D >& /*xMapping*/ ) + { + // TODO + return uno::Reference< rendering::XCachedPrimitive >(nullptr); + } + + uno::Reference< rendering::XCanvasFont > CanvasHelper::createFont( const rendering::XCanvas* /*pCanvas*/, + const rendering::FontRequest& fontRequest, + const uno::Sequence< beans::PropertyValue >& extraFontProperties, + const geometry::Matrix2D& fontMatrix ) + { + if( needOutput() ) + { + return uno::Reference< rendering::XCanvasFont >( + new CanvasFont(fontRequest, extraFontProperties, fontMatrix ) ); + } + + return uno::Reference< rendering::XCanvasFont >(); + } + + uno::Sequence< rendering::FontInfo > CanvasHelper::queryAvailableFonts( const rendering::XCanvas* /*pCanvas*/, + const rendering::FontInfo& /*aFilter*/, + const uno::Sequence< beans::PropertyValue >& /*aFontProperties*/ ) + { + // TODO + return uno::Sequence< rendering::FontInfo >(); + } + + uno::Reference< rendering::XCachedPrimitive > CanvasHelper::drawText( const rendering::XCanvas* /*pCanvas*/, + const rendering::StringContext& text, + const uno::Reference< rendering::XCanvasFont >& xFont, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState, + sal_Int8 /*textDirection*/ ) + { + ENSURE_OR_THROW( xFont.is(), + "CanvasHelper::drawText: font is NULL"); + + if( needOutput() ) + { + GraphicsSharedPtr pGraphics( mpGraphicsProvider->getGraphics() ); + + setupGraphicsState( pGraphics, viewState, renderState ); + + Gdiplus::SolidBrush aBrush( + Gdiplus::Color( + tools::sequenceToArgb(renderState.DeviceColor))); + + CanvasFont::ImplRef pFont( + tools::canvasFontFromXFont(xFont) ); + + // Move glyphs up, such that output happens at the font + // baseline. + Gdiplus::PointF aPoint( 0.0, + static_cast<Gdiplus::REAL>(-(pFont->getFont()->GetSize()* + pFont->getCellAscent() / + pFont->getEmHeight())) ); + + // TODO(F1): According to + // http://support.microsoft.com/default.aspx?scid=kb;EN-US;Q307208, + // we might have to revert to GDI and ExTextOut here, + // since GDI+ takes the scalability a little bit too + // far... + + // TODO(F2): Proper layout (BiDi, CTL)! IMHO must use + // DrawDriverString here, and perform layouting myself... + ENSURE_OR_THROW( + Gdiplus::Ok == pGraphics->DrawString( o3tl::toW(text.Text.copy( text.StartPosition, + text.Length ).getStr()), + text.Length, + pFont->getFont().get(), + aPoint, + &aBrush ), + "CanvasHelper::drawText(): GDI+ call failed" ); + } + + return uno::Reference< rendering::XCachedPrimitive >(nullptr); + } + + uno::Reference< rendering::XCachedPrimitive > CanvasHelper::drawTextLayout( const rendering::XCanvas* /*pCanvas*/, + const uno::Reference< rendering::XTextLayout >& xLayoutetText, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState ) + { + ENSURE_OR_THROW( xLayoutetText.is(), + "CanvasHelper::drawTextLayout: layout is NULL"); + + if( needOutput() ) + { + TextLayout* pTextLayout = + dynamic_cast< TextLayout* >( xLayoutetText.get() ); + + ENSURE_OR_THROW( pTextLayout, + "CanvasHelper::drawTextLayout(): TextLayout not compatible with this canvas" ); + + pTextLayout->draw( mpGraphicsProvider->getGraphics(), + viewState, + renderState, + maOutputOffset, + mpDevice, + false ); + } + + return uno::Reference< rendering::XCachedPrimitive >(nullptr); + } + + uno::Reference< rendering::XCachedPrimitive > CanvasHelper::drawBitmap( const rendering::XCanvas* /*pCanvas*/, + const uno::Reference< rendering::XBitmap >& xBitmap, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState ) + { + ENSURE_OR_THROW( xBitmap.is(), + "CanvasHelper::drawBitmap: bitmap is NULL"); + + if( needOutput() ) + { + // check whether one of our own objects - need to retrieve + // bitmap _before_ calling + // GraphicsProvider::getGraphics(), to avoid locking our + // own surface. + BitmapSharedPtr pGdiBitmap; + BitmapProvider* pBitmap = dynamic_cast< BitmapProvider* >(xBitmap.get()); + if( pBitmap ) + { + IBitmapSharedPtr pDXBitmap( pBitmap->getBitmap() ); + if( pDXBitmap ) + pGdiBitmap = pDXBitmap->getBitmap(); + } + + GraphicsSharedPtr pGraphics( mpGraphicsProvider->getGraphics() ); + setupGraphicsState( pGraphics, viewState, renderState ); + + if( pGdiBitmap ) + tools::drawGdiPlusBitmap(pGraphics,pGdiBitmap); + else + tools::drawVCLBitmapFromXBitmap(pGraphics, + xBitmap); + } + + // TODO(P1): Provide caching here. + return uno::Reference< rendering::XCachedPrimitive >(nullptr); + } + + uno::Reference< rendering::XCachedPrimitive > CanvasHelper::drawBitmapModulated( const rendering::XCanvas* pCanvas, + const uno::Reference< rendering::XBitmap >& xBitmap, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState ) + { + ENSURE_OR_THROW( xBitmap.is(), + "CanvasHelper::drawBitmap: bitmap is NULL"); + + // no color set -> this is equivalent to a plain drawBitmap(), then + if( renderState.DeviceColor.getLength() < 3 ) + return drawBitmap( pCanvas, xBitmap, viewState, renderState ); + + if( needOutput() ) + { + GraphicsSharedPtr pGraphics( mpGraphicsProvider->getGraphics() ); + + setupGraphicsState( pGraphics, viewState, renderState ); + + BitmapSharedPtr pBitmap( tools::bitmapFromXBitmap( xBitmap ) ); + Gdiplus::Rect aRect( 0, 0, + pBitmap->GetWidth(), + pBitmap->GetHeight() ); + + // Setup an ImageAttributes with an alpha-modulating + // color matrix. + rendering::ARGBColor aARGBColor( + mpDevice->getDeviceColorSpace()->convertToARGB(renderState.DeviceColor)[0]); + + Gdiplus::ImageAttributes aImgAttr; + tools::setModulateImageAttributes( aImgAttr, + aARGBColor.Red, + aARGBColor.Green, + aARGBColor.Blue, + aARGBColor.Alpha ); + + ENSURE_OR_THROW( + Gdiplus::Ok == pGraphics->DrawImage( pBitmap.get(), + aRect, + 0, 0, + pBitmap->GetWidth(), + pBitmap->GetHeight(), + Gdiplus::UnitPixel, + &aImgAttr ), + "CanvasHelper::drawBitmapModulated(): GDI+ call failed" ); + } + + // TODO(P1): Provide caching here. + return uno::Reference< rendering::XCachedPrimitive >(nullptr); + } + + uno::Reference< rendering::XGraphicDevice > CanvasHelper::getDevice() + { + return uno::Reference< rendering::XGraphicDevice >(mpDevice); + } + + // private helper + + + Gdiplus::CompositingMode CanvasHelper::calcCompositingMode( sal_Int8 nMode ) + { + Gdiplus::CompositingMode aRet( Gdiplus::CompositingModeSourceOver ); + + switch( nMode ) + { + case rendering::CompositeOperation::OVER: + case rendering::CompositeOperation::CLEAR: + aRet = Gdiplus::CompositingModeSourceOver; + break; + + case rendering::CompositeOperation::SOURCE: + aRet = Gdiplus::CompositingModeSourceCopy; + break; + + case rendering::CompositeOperation::DESTINATION: + case rendering::CompositeOperation::UNDER: + case rendering::CompositeOperation::INSIDE: + case rendering::CompositeOperation::INSIDE_REVERSE: + case rendering::CompositeOperation::OUTSIDE: + case rendering::CompositeOperation::OUTSIDE_REVERSE: + case rendering::CompositeOperation::ATOP: + case rendering::CompositeOperation::ATOP_REVERSE: + case rendering::CompositeOperation::XOR: + case rendering::CompositeOperation::ADD: + case rendering::CompositeOperation::SATURATE: + // TODO(F2): Problem, because GDI+ only knows about two compositing modes + aRet = Gdiplus::CompositingModeSourceOver; + break; + + default: + ENSURE_OR_THROW( false, "CanvasHelper::calcCompositingMode: unexpected mode" ); + break; + } + + return aRet; + } + + void CanvasHelper::setupGraphicsState( GraphicsSharedPtr const & rGraphics, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState ) + { + ENSURE_OR_THROW( needOutput(), + "CanvasHelper::setupGraphicsState: primary graphics invalid" ); + ENSURE_OR_THROW( mpDevice, + "CanvasHelper::setupGraphicsState: reference device invalid" ); + + // setup view transform first. Clipping e.g. depends on it + ::basegfx::B2DHomMatrix aTransform; + ::canvas::tools::getViewStateTransform(aTransform, viewState); + + // add output offset + if( !maOutputOffset.equalZero() ) + { + const basegfx::B2DHomMatrix aOutputOffset(basegfx::utils::createTranslateB2DHomMatrix( + maOutputOffset.getX(), maOutputOffset.getY())); + aTransform = aOutputOffset * aTransform; + } + + Gdiplus::Matrix aMatrix; + tools::gdiPlusMatrixFromB2DHomMatrix( aMatrix, aTransform ); + + ENSURE_OR_THROW( + Gdiplus::Ok == rGraphics->SetTransform( &aMatrix ), + "CanvasHelper::setupGraphicsState(): Failed to set GDI+ transformation" ); + + // setup view and render state clipping + ENSURE_OR_THROW( + Gdiplus::Ok == rGraphics->ResetClip(), + "CanvasHelper::setupGraphicsState(): Failed to reset GDI+ clip" ); + + if( viewState.Clip.is() ) + { + GraphicsPathSharedPtr aClipPath( tools::graphicsPathFromXPolyPolygon2D( viewState.Clip ) ); + + // TODO(P3): Cache clip. SetClip( GraphicsPath ) performs abyssmally on GDI+. + // Try SetClip( Rect ) or similar for simple clip paths (need some support in + // LinePolyPolygon, then) + ENSURE_OR_THROW( + Gdiplus::Ok == rGraphics->SetClip( aClipPath.get(), + Gdiplus::CombineModeIntersect ), + "CanvasHelper::setupGraphicsState(): Cannot set GDI+ clip" ); + } + + // setup overall transform only now. View clip above was relative to + // view transform + ::canvas::tools::mergeViewAndRenderTransform(aTransform, + viewState, + renderState); + + // add output offset + if( !maOutputOffset.equalZero() ) + { + const basegfx::B2DHomMatrix aOutputOffset(basegfx::utils::createTranslateB2DHomMatrix( + maOutputOffset.getX(), maOutputOffset.getY())); + aTransform = aOutputOffset * aTransform; + } + + tools::gdiPlusMatrixFromB2DHomMatrix( aMatrix, aTransform ); + + ENSURE_OR_THROW( + Gdiplus::Ok == rGraphics->SetTransform( &aMatrix ), + "CanvasHelper::setupGraphicsState(): Cannot set GDI+ transformation" ); + + if( renderState.Clip.is() ) + { + GraphicsPathSharedPtr aClipPath( tools::graphicsPathFromXPolyPolygon2D( renderState.Clip ) ); + + // TODO(P3): Cache clip. SetClip( GraphicsPath ) performs abyssmally on GDI+. + // Try SetClip( Rect ) or similar for simple clip paths (need some support in + // LinePolyPolygon, then) + ENSURE_OR_THROW( + Gdiplus::Ok == rGraphics->SetClip( aClipPath.get(), + Gdiplus::CombineModeIntersect ), + "CanvasHelper::setupGraphicsState(): Cannot set GDI+ clip" ); + } + + // setup compositing + const Gdiplus::CompositingMode eCompositing( calcCompositingMode( renderState.CompositeOperation ) ); + ENSURE_OR_THROW( + Gdiplus::Ok == rGraphics->SetCompositingMode( eCompositing ), + "CanvasHelper::setupGraphicsState(): Cannot set GDI* compositing mode)" ); + } + + void CanvasHelper::flush() const + { + if( needOutput() ) + mpGraphicsProvider->getGraphics()->Flush( Gdiplus::FlushIntentionSync ); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_canvashelper.hxx b/canvas/source/directx/dx_canvashelper.hxx new file mode 100644 index 000000000..54731a08e --- /dev/null +++ b/canvas/source/directx/dx_canvashelper.hxx @@ -0,0 +1,252 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include <com/sun/star/rendering/XCanvas.hpp> +#include <com/sun/star/rendering/XBitmapCanvas.hpp> +#include <com/sun/star/rendering/XIntegerBitmap.hpp> + +#include <basegfx/vector/b2isize.hxx> +#include <basegfx/vector/b2dsize.hxx> + +#include "dx_graphicsprovider.hxx" +#include "dx_gdiplususer.hxx" +#include "dx_impltools.hxx" + + +namespace dxcanvas +{ + /** Helper class for basic canvas functionality. Also offers + optional backbuffer painting, when providing it with a second + HDC to render into. + */ + class CanvasHelper + { + public: + CanvasHelper(); + + /// make noncopyable + CanvasHelper(const CanvasHelper&) = delete; + const CanvasHelper& operator=(const CanvasHelper&) = delete; + + /// Release all references + void disposing(); + + /** Initialize canvas helper + + This method late-initializes the canvas helper, providing + it with the necessary device and output objects. Note that + the CanvasHelper does <em>not</em> take ownership of the + passed rDevice reference, nor does it perform any + reference counting. Thus, to prevent the reference counted + SpriteCanvas object from deletion, the user of this class + is responsible for holding ref-counted references itself! + + @param rDevice + Reference device this canvas is associated with + + */ + void setDevice( css::rendering::XGraphicDevice& rDevice ); + + /** Set the target for rendering operations + + @param rTarget + Render target + */ + void setTarget( const GraphicsProviderSharedPtr& rTarget ); + + /** Set the target for rendering operations + + @param rTarget + Render target + + @param rOutputOffset + Output offset in pixel + */ + void setTarget( const GraphicsProviderSharedPtr& rTarget, + const ::basegfx::B2ISize& rOutputOffset ); + + + // CanvasHelper functionality + // ========================== + + // XCanvas (only providing, not implementing the + // interface. Also note subtle method parameter differences) + void clear(); + void drawPoint( const css::rendering::XCanvas* pCanvas, + const css::geometry::RealPoint2D& aPoint, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState ); + void drawLine( const css::rendering::XCanvas* pCanvas, + const css::geometry::RealPoint2D& aStartPoint, + const css::geometry::RealPoint2D& aEndPoint, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState ); + void drawBezier( const css::rendering::XCanvas* pCanvas, + const css::geometry::RealBezierSegment2D& aBezierSegment, + const css::geometry::RealPoint2D& aEndPoint, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState ); + css::uno::Reference< css::rendering::XCachedPrimitive > + drawPolyPolygon( const css::rendering::XCanvas* pCanvas, + const css::uno::Reference< + css::rendering::XPolyPolygon2D >& xPolyPolygon, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState ); + css::uno::Reference< css::rendering::XCachedPrimitive > + strokePolyPolygon( const css::rendering::XCanvas* pCanvas, + const css::uno::Reference< + css::rendering::XPolyPolygon2D >& xPolyPolygon, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState, + const css::rendering::StrokeAttributes& strokeAttributes ); + css::uno::Reference< css::rendering::XCachedPrimitive > + strokeTexturedPolyPolygon( const css::rendering::XCanvas* pCanvas, + const css::uno::Reference< + css::rendering::XPolyPolygon2D >& xPolyPolygon, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState, + const css::uno::Sequence< + css::rendering::Texture >& textures, + const css::rendering::StrokeAttributes& strokeAttributes ); + css::uno::Reference< css::rendering::XCachedPrimitive > + strokeTextureMappedPolyPolygon( const css::rendering::XCanvas* pCanvas, + const css::uno::Reference< + css::rendering::XPolyPolygon2D >& xPolyPolygon, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState, + const css::uno::Sequence< + css::rendering::Texture >& textures, + const css::uno::Reference< + css::geometry::XMapping2D >& xMapping, + const css::rendering::StrokeAttributes& strokeAttributes ); + css::uno::Reference< css::rendering::XPolyPolygon2D > + queryStrokeShapes( const css::rendering::XCanvas* pCanvas, + const css::uno::Reference< + css::rendering::XPolyPolygon2D >& xPolyPolygon, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState, + const css::rendering::StrokeAttributes& strokeAttributes ); + css::uno::Reference< css::rendering::XCachedPrimitive > + fillPolyPolygon( const css::rendering::XCanvas* pCanvas, + const css::uno::Reference< + css::rendering::XPolyPolygon2D >& xPolyPolygon, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState ); + css::uno::Reference< css::rendering::XCachedPrimitive > + fillTexturedPolyPolygon( const css::rendering::XCanvas* pCanvas, + const css::uno::Reference< + css::rendering::XPolyPolygon2D >& xPolyPolygon, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState, + const css::uno::Sequence< + css::rendering::Texture >& textures ); + css::uno::Reference< css::rendering::XCachedPrimitive > + fillTextureMappedPolyPolygon( const css::rendering::XCanvas* pCanvas, + const css::uno::Reference< + css::rendering::XPolyPolygon2D >& xPolyPolygon, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState, + const css::uno::Sequence< + css::rendering::Texture >& textures, + const css::uno::Reference< + css::geometry::XMapping2D >& xMapping ); + + css::uno::Reference< css::rendering::XCanvasFont > + createFont( const css::rendering::XCanvas* pCanvas, + const css::rendering::FontRequest& fontRequest, + const css::uno::Sequence< + css::beans::PropertyValue >& extraFontProperties, + const css::geometry::Matrix2D& fontMatrix ); + + css::uno::Sequence< css::rendering::FontInfo > + queryAvailableFonts( const css::rendering::XCanvas* pCanvas, + const css::rendering::FontInfo& aFilter, + const css::uno::Sequence< + css::beans::PropertyValue >& aFontProperties ); + + css::uno::Reference< css::rendering::XCachedPrimitive > + drawText( const css::rendering::XCanvas* pCanvas, + const css::rendering::StringContext& text, + const css::uno::Reference< + css::rendering::XCanvasFont >& xFont, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState, + sal_Int8 textDirection ); + + css::uno::Reference< css::rendering::XCachedPrimitive > + drawTextLayout( const css::rendering::XCanvas* pCanvas, + const css::uno::Reference< + css::rendering::XTextLayout >& laidOutText, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState ); + + css::uno::Reference< css::rendering::XCachedPrimitive > + drawBitmap( const css::rendering::XCanvas* pCanvas, + const css::uno::Reference< + css::rendering::XBitmap >& xBitmap, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState ); + css::uno::Reference< css::rendering::XCachedPrimitive > + drawBitmapModulated( const css::rendering::XCanvas* pCanvas, + const css::uno::Reference< + css::rendering::XBitmap >& xBitmap, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState ); + css::uno::Reference< css::rendering::XGraphicDevice > + getDevice(); + + // Flush drawing queue to screen + void flush() const; + + /** Called from XCanvas base classes, to notify that content + is _about_ to change + */ + void modifying() {} + + protected: + /// Refcounted global GDI+ state container + GDIPlusUserSharedPtr mpGdiPlusUser; + + /** Phyical output device + + Deliberately not a refcounted reference, because of + potential circular references for spritecanvas. + */ + css::rendering::XGraphicDevice* mpDevice; + + /// Provides the Gdiplus::Graphics to render into + GraphicsProviderSharedPtr mpGraphicsProvider; + + bool needOutput() const { return bool(mpGraphicsProvider); }; + + // returns transparency of color + void setupGraphicsState( GraphicsSharedPtr const & rGraphics, + const css::rendering::ViewState& viewState, + const css::rendering::RenderState& renderState ); + + Gdiplus::CompositingMode calcCompositingMode( sal_Int8 nMode ); + + /// Current (transformation-independent) output buffer offset + ::basegfx::B2ISize maOutputOffset; + }; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_canvashelper_texturefill.cxx b/canvas/source/directx/dx_canvashelper_texturefill.cxx new file mode 100644 index 000000000..0bea70fc8 --- /dev/null +++ b/canvas/source/directx/dx_canvashelper_texturefill.cxx @@ -0,0 +1,608 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <cstdlib> +#include <memory> +#include <tuple> + +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <basegfx/numeric/ftools.hxx> +#include <basegfx/point/b2dpoint.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/range/b2drectangle.hxx> +#include <basegfx/utils/canvastools.hxx> +#include <basegfx/utils/keystoplerp.hxx> +#include <basegfx/utils/lerp.hxx> +#include <basegfx/utils/tools.hxx> +#include <com/sun/star/rendering/TexturingMode.hpp> +#include <rtl/math.hxx> +#include <tools/diagnose_ex.h> + +#include <parametricpolypolygon.hxx> + +#include "dx_canvashelper.hxx" +#include "dx_impltools.hxx" +#include "dx_spritecanvas.hxx" + + +using namespace ::com::sun::star; + +namespace dxcanvas +{ + namespace + { + typedef std::shared_ptr< Gdiplus::PathGradientBrush > PathGradientBrushSharedPtr; + + bool fillLinearGradient( GraphicsSharedPtr const & rGraphics, + const ::canvas::ParametricPolyPolygon::Values& /*rValues*/, + const std::vector< Gdiplus::Color >& rColors, + const std::vector< Gdiplus::REAL >& rStops, + const GraphicsPathSharedPtr& rFillPath, + const rendering::Texture& texture ) + { + // setup a linear gradient with given colors + + + Gdiplus::LinearGradientBrush aBrush( + Gdiplus::PointF(0.0f, + 0.5f), + Gdiplus::PointF(1.0f, + 0.5f), + rColors[0], + rColors[1] ); + + aBrush.SetInterpolationColors(rColors.data(), + rStops.data(), + rColors.size()); + + // render background color, as LinearGradientBrush does not + // properly support the WrapModeClamp repeat mode + Gdiplus::SolidBrush aBackgroundBrush( rColors[0] ); + rGraphics->FillPath( &aBackgroundBrush, rFillPath.get() ); + + // TODO(F2): This does not yet support other repeat modes + // except clamp, and probably also no multi-texturing + + // calculate parallelogram of gradient in object space, extend + // top and bottom of it such that they cover the whole fill + // path bound area + ::basegfx::B2DHomMatrix aTextureTransform; + ::basegfx::unotools::homMatrixFromAffineMatrix( aTextureTransform, + texture.AffineTransform ); + + ::basegfx::B2DPoint aLeftTop( 0.0, 0.0 ); + ::basegfx::B2DPoint aLeftBottom( 0.0, 1.0 ); + ::basegfx::B2DPoint aRightTop( 1.0, 0.0 ); + ::basegfx::B2DPoint aRightBottom( 1.0, 1.0 ); + + aLeftTop *= aTextureTransform; + aLeftBottom *= aTextureTransform; + aRightTop *= aTextureTransform; + aRightBottom*= aTextureTransform; + + Gdiplus::RectF aBounds; + rFillPath->GetBounds( &aBounds ); + + // now, we potentially have to enlarge our gradient area + // atop and below the transformed [0,1]x[0,1] unit rect, + // for the gradient to fill the complete bound rect. + ::basegfx::utils::infiniteLineFromParallelogram( aLeftTop, + aLeftBottom, + aRightTop, + aRightBottom, + tools::b2dRangeFromGdiPlusRectF( aBounds ) ); + + // calc length of bound rect diagonal + const double nDiagonalLength( + hypot( aBounds.Width, + aBounds.Height ) ); + + // generate a path which covers the 'right' side of the + // gradient, extending two times the bound rect diagonal to + // the right (and thus covering the whole half plane 'right' + // of the gradient). Take the middle of the gradient as the + // 'left' side of the polygon, to not fall victim to rounding + // errors at the edge. + ::basegfx::B2DVector aDirection( aLeftTop - aLeftBottom ); + aDirection = ::basegfx::getNormalizedPerpendicular( aDirection ); + aDirection *= nDiagonalLength; + + const ::basegfx::B2DPoint aHalfPlaneLeftTop( (aLeftTop + aRightTop) * 0.5 ); + const ::basegfx::B2DPoint aHalfPlaneLeftBottom( (aLeftBottom + aRightBottom) * 0.5 ); + const ::basegfx::B2DPoint aHalfPlaneRightTop( aRightTop + aDirection ); + const ::basegfx::B2DPoint aHalfPlaneRightBottom( aRightBottom + aDirection ); + + Gdiplus::GraphicsPath aSolidFillPath; + aSolidFillPath.AddLine( static_cast<Gdiplus::REAL>(aHalfPlaneLeftTop.getX()), + static_cast<Gdiplus::REAL>(aHalfPlaneLeftTop.getY()), + static_cast<Gdiplus::REAL>(aHalfPlaneRightTop.getX()), + static_cast<Gdiplus::REAL>(aHalfPlaneRightTop.getY()) ); + aSolidFillPath.AddLine( static_cast<Gdiplus::REAL>(aHalfPlaneRightBottom.getX()), + static_cast<Gdiplus::REAL>(aHalfPlaneRightBottom.getY()), + static_cast<Gdiplus::REAL>(aHalfPlaneLeftBottom.getX()), + static_cast<Gdiplus::REAL>(aHalfPlaneLeftBottom.getY()) ); + aSolidFillPath.CloseFigure(); + + // limit output to fill path, we've just generated a path that + // might be substantially larger + if( Gdiplus::Ok != rGraphics->SetClip( rFillPath.get(), + Gdiplus::CombineModeIntersect ) ) + { + return false; + } + + Gdiplus::SolidBrush aBackgroundBrush2( rColors.back() ); + rGraphics->FillPath( &aBackgroundBrush2, &aSolidFillPath ); + + // generate clip polygon from the extended parallelogram + // (exploit the feature that distinct lines in a figure are + // automatically closed by a straight line) + Gdiplus::GraphicsPath aClipPath; + aClipPath.AddLine( static_cast<Gdiplus::REAL>(aLeftTop.getX()), + static_cast<Gdiplus::REAL>(aLeftTop.getY()), + static_cast<Gdiplus::REAL>(aRightTop.getX()), + static_cast<Gdiplus::REAL>(aRightTop.getY()) ); + aClipPath.AddLine( static_cast<Gdiplus::REAL>(aRightBottom.getX()), + static_cast<Gdiplus::REAL>(aRightBottom.getY()), + static_cast<Gdiplus::REAL>(aLeftBottom.getX()), + static_cast<Gdiplus::REAL>(aLeftBottom.getY()) ); + aClipPath.CloseFigure(); + + // limit output to a _single_ strip of the gradient (have to + // clip here, since GDI+ wrapmode clamp does not work here) + if( Gdiplus::Ok != rGraphics->SetClip( &aClipPath, + Gdiplus::CombineModeIntersect ) ) + { + return false; + } + + // now, finally, output the gradient + Gdiplus::Matrix aMatrix; + tools::gdiPlusMatrixFromAffineMatrix2D( aMatrix, + texture.AffineTransform ); + aBrush.SetTransform( &aMatrix ); + + rGraphics->FillRectangle( &aBrush, aBounds ); + + return true; + } + + int numColorSteps( const Gdiplus::Color& rColor1, const Gdiplus::Color& rColor2 ) + { + return std::max( + std::abs( rColor1.GetRed() - rColor2.GetRed() ), + std::max( + std::abs( rColor1.GetGreen() - rColor2.GetGreen() ), + std::abs( rColor1.GetBlue() - rColor2.GetBlue() ) ) ); + } + + bool fillPolygonalGradient( const ::canvas::ParametricPolyPolygon::Values& rValues, + const std::vector< Gdiplus::Color >& rColors, + const std::vector< Gdiplus::REAL >& rStops, + GraphicsSharedPtr const & rGraphics, + const GraphicsPathSharedPtr& rPath, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState, + const rendering::Texture& texture ) + { + // copy original fill path object, might have to change it + // below + GraphicsPathSharedPtr pFillPath( rPath ); + const ::basegfx::B2DPolygon& rGradientPoly( rValues.maGradientPoly ); + + PathGradientBrushSharedPtr pGradientBrush; + + // fill background uniformly with end color + Gdiplus::SolidBrush aBackgroundBrush( rColors[0] ); + rGraphics->FillPath( &aBackgroundBrush, pFillPath.get() ); + + Gdiplus::Matrix aMatrix; + // scale focus according to aspect ratio: for wider-than-tall + // bounds (nAspectRatio > 1.0), the focus must have non-zero + // width. Specifically, a bound rect twice as wide as tall has + // a focus of half its width. + if( !::rtl::math::approxEqual(rValues.mnAspectRatio, + 1.0) ) + { + // KLUDGE 1: + + // And here comes the greatest shortcoming of the GDI+ + // gradients ever: SetFocusScales completely ignores + // transformations, both when set at the PathGradientBrush + // and for the world coordinate system. Thus, to correctly + // display anisotrophic path gradients, we have to render + // them by hand. WTF. + + // TODO(F2): This does not yet support other repeat modes + // except clamp, and probably also no multi-texturing + + // limit output to to-be-filled polygon + if( Gdiplus::Ok != rGraphics->SetClip( pFillPath.get(), + Gdiplus::CombineModeIntersect ) ) + { + return false; + } + + // disable anti-aliasing, if any + const Gdiplus::SmoothingMode eOldAAMode( rGraphics->GetSmoothingMode() ); + rGraphics->SetSmoothingMode( Gdiplus::SmoothingModeHighSpeed ); + + + // determine number of steps to use + + + // TODO(Q2): Unify step calculations with VCL canvas + int nColorSteps = 0; + for( size_t i=0; i<rColors.size()-1; ++i ) + nColorSteps += numColorSteps(rColors[i],rColors[i+1]); + ::basegfx::B2DHomMatrix aTotalTransform; + const int nStepCount= + ::canvas::tools::calcGradientStepCount(aTotalTransform, + viewState, + renderState, + texture, + nColorSteps); + + ::basegfx::B2DHomMatrix aTextureTransform; + ::basegfx::unotools::homMatrixFromAffineMatrix( aTextureTransform, + texture.AffineTransform ); + // determine overall transformation for inner polygon (might + // have to be prefixed by anisotrophic scaling) + ::basegfx::B2DHomMatrix aInnerPolygonTransformMatrix; + + // For performance reasons, we create a temporary VCL polygon + // here, keep it all the way and only change the vertex values + // in the loop below (as ::Polygon is a pimpl class, creating + // one every loop turn would really stress the mem allocator) + ::basegfx::B2DPolygon aOuterPoly( rGradientPoly ); + ::basegfx::B2DPolygon aInnerPoly; + + // subdivide polygon _before_ rendering, would otherwise have + // to be performed on every loop turn. + if( aOuterPoly.areControlPointsUsed() ) + aOuterPoly = ::basegfx::utils::adaptiveSubdivideByAngle(aOuterPoly); + + aInnerPoly = aOuterPoly; + aOuterPoly.transform(aTextureTransform); + + + // apply scaling (possibly anisotrophic) to inner polygon + + + // scale inner polygon according to aspect ratio: for + // wider-than-tall bounds (nAspectRatio > 1.0), the inner + // polygon, representing the gradient focus, must have + // non-zero width. Specifically, a bound rect twice as wide as + // tall has a focus polygon of half its width. + const double nAspectRatio( rValues.mnAspectRatio ); + if( nAspectRatio > 1.0 ) + { + // width > height case + aInnerPolygonTransformMatrix.scale( 1.0 - 1.0/nAspectRatio, + 0.0 ); + } + else if( nAspectRatio < 1.0 ) + { + // width < height case + aInnerPolygonTransformMatrix.scale( 0.0, + 1.0 - nAspectRatio ); + } + else + { + // isotrophic case + aInnerPolygonTransformMatrix.scale( 0.0, 0.0 ); + } + + // and finally, add texture transform to it. + aInnerPolygonTransformMatrix *= aTextureTransform; + + // apply final matrix to polygon + aInnerPoly.transform( aInnerPolygonTransformMatrix ); + + Gdiplus::GraphicsPath aCurrPath; + Gdiplus::SolidBrush aFillBrush( rColors[0] ); + const sal_uInt32 nNumPoints( aOuterPoly.count() ); + basegfx::utils::KeyStopLerp aLerper(rValues.maStops); + for( int i=1; i<nStepCount; ++i ) + { + std::ptrdiff_t nIndex; + double fAlpha; + const double fT( i/double(nStepCount) ); + std::tie(nIndex,fAlpha)=aLerper.lerp(fT); + + const Gdiplus::Color aFillColor( + static_cast<BYTE>( basegfx::utils::lerp(rColors[nIndex].GetRed(),rColors[nIndex+1].GetRed(),fAlpha) ), + static_cast<BYTE>( basegfx::utils::lerp(rColors[nIndex].GetGreen(),rColors[nIndex+1].GetGreen(),fAlpha) ), + static_cast<BYTE>( basegfx::utils::lerp(rColors[nIndex].GetBlue(),rColors[nIndex+1].GetBlue(),fAlpha) ) ); + + aFillBrush.SetColor( aFillColor ); + aCurrPath.Reset(); aCurrPath.StartFigure(); + for( unsigned int p=1; p<nNumPoints; ++p ) + { + const ::basegfx::B2DPoint& rOuterPoint1( aOuterPoly.getB2DPoint(p-1) ); + const ::basegfx::B2DPoint& rInnerPoint1( aInnerPoly.getB2DPoint(p-1) ); + const ::basegfx::B2DPoint& rOuterPoint2( aOuterPoly.getB2DPoint(p) ); + const ::basegfx::B2DPoint& rInnerPoint2( aInnerPoly.getB2DPoint(p) ); + + aCurrPath.AddLine( + Gdiplus::REAL(fT*rInnerPoint1.getX() + (1-fT)*rOuterPoint1.getX()), + Gdiplus::REAL(fT*rInnerPoint1.getY() + (1-fT)*rOuterPoint1.getY()), + Gdiplus::REAL(fT*rInnerPoint2.getX() + (1-fT)*rOuterPoint2.getX()), + Gdiplus::REAL(fT*rInnerPoint2.getY() + (1-fT)*rOuterPoint2.getY())); + } + aCurrPath.CloseFigure(); + + rGraphics->FillPath( &aFillBrush, &aCurrPath ); + } + + // reset to old anti-alias mode + rGraphics->SetSmoothingMode( eOldAAMode ); + } + else + { + // KLUDGE 2: + + // We're generating a PathGradientBrush from scratch here, + // and put in a transformed GraphicsPath (transformed with + // the texture transform). This is because the + // straight-forward approach to store a Brush pointer at + // this class and set a texture transform via + // PathGradientBrush::SetTransform() is spoiled by MS: it + // seems that _either_ the texture transform, _or_ the + // transform at the Graphics can be set, but not both. If + // one sets both, only the translational components of the + // texture is respected. + + tools::gdiPlusMatrixFromAffineMatrix2D( aMatrix, + texture.AffineTransform ); + GraphicsPathSharedPtr pGradientPath( + tools::graphicsPathFromB2DPolygon( rValues.maGradientPoly )); + pGradientPath->Transform( &aMatrix ); + + pGradientBrush + = std::make_shared<Gdiplus::PathGradientBrush>( pGradientPath.get() ); + pGradientBrush->SetInterpolationColors( rColors.data(), + rStops.data(), + rStops.size() ); + + // explicitly setup center point. Since the center of GDI+ + // gradients are by default the _centroid_ of the path + // (i.e. the weighted sum of edge points), it will not + // necessarily coincide with our notion of center. + Gdiplus::PointF aCenterPoint(0, 0); + aMatrix.TransformPoints( &aCenterPoint ); + pGradientBrush->SetCenterPoint( aCenterPoint ); + + const bool bTileX( texture.RepeatModeX != rendering::TexturingMode::CLAMP ); + const bool bTileY( texture.RepeatModeY != rendering::TexturingMode::CLAMP ); + + if( bTileX && bTileY ) + pGradientBrush->SetWrapMode( Gdiplus::WrapModeTile ); + else + { + OSL_ENSURE( bTileY == bTileX, + "ParametricPolyPolygon::fillPolygonalGradient(): Cannot have repeat x and repeat y differ!" ); + + pGradientBrush->SetWrapMode( Gdiplus::WrapModeClamp ); + } + + // render actual gradient + rGraphics->FillPath( pGradientBrush.get(), pFillPath.get() ); + } + +#if OSL_DEBUG_LEVEL > 0 + Gdiplus::Pen aPen( Gdiplus::Color( 255, 255, 0, 0 ), + 0.0001f ); + + rGraphics->DrawRectangle( &aPen, + Gdiplus::RectF( 0.0f, 0.0f, + 1.0f, 1.0f ) ); +#endif + + return true; + } + + bool fillGradient( const ::canvas::ParametricPolyPolygon::Values& rValues, + const std::vector< Gdiplus::Color >& rColors, + const std::vector< Gdiplus::REAL >& rStops, + GraphicsSharedPtr const & rGraphics, + const GraphicsPathSharedPtr& rPath, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState, + const rendering::Texture& texture ) + { + switch( rValues.meType ) + { + case ::canvas::ParametricPolyPolygon::GradientType::Linear: + fillLinearGradient( rGraphics, + rValues, + rColors, + rStops, + rPath, + texture ); + break; + + case ::canvas::ParametricPolyPolygon::GradientType::Elliptical: + case ::canvas::ParametricPolyPolygon::GradientType::Rectangular: + fillPolygonalGradient( rValues, + rColors, + rStops, + rGraphics, + rPath, + viewState, + renderState, + texture ); + break; + + default: + ENSURE_OR_THROW( false, + "CanvasHelper::fillGradient(): Unexpected case" ); + } + + return true; + } + + void fillBitmap( const uno::Reference< rendering::XBitmap >& xBitmap, + GraphicsSharedPtr const & rGraphics, + const GraphicsPathSharedPtr& rPath, + const rendering::Texture& rTexture ) + { + OSL_ENSURE( rTexture.RepeatModeX == + rTexture.RepeatModeY, + "CanvasHelper::fillBitmap(): GDI+ cannot handle differing X/Y repeat mode." ); + + const bool bClamp( rTexture.RepeatModeX == rendering::TexturingMode::NONE && + rTexture.RepeatModeY == rendering::TexturingMode::NONE ); + + const geometry::IntegerSize2D aBmpSize( xBitmap->getSize() ); + ENSURE_ARG_OR_THROW( aBmpSize.Width != 0 && + aBmpSize.Height != 0, + "CanvasHelper::fillBitmap(): zero-sized texture bitmap" ); + + // TODO(P3): Detect case that path is rectangle and + // bitmap is just scaled into that. Then, we can + // render directly, without generating a temporary + // GDI+ bitmap (this is significant, because drawing + // layer presents background object bitmap in that + // way!) + BitmapSharedPtr pBitmap( + tools::bitmapFromXBitmap( xBitmap ) ); + + TextureBrushSharedPtr pBrush; + if( ::rtl::math::approxEqual( rTexture.Alpha, + 1.0 ) ) + { + pBrush = std::make_shared<Gdiplus::TextureBrush>( + pBitmap.get(), + bClamp ? Gdiplus::WrapModeClamp : Gdiplus::WrapModeTile ); + } + else + { + Gdiplus::ImageAttributes aImgAttr; + + tools::setModulateImageAttributes( aImgAttr, + 1.0, + 1.0, + 1.0, + rTexture.Alpha ); + + Gdiplus::Rect aRect(0,0, + aBmpSize.Width, + aBmpSize.Height); + pBrush = std::make_shared<Gdiplus::TextureBrush>( + pBitmap.get(), + aRect, + &aImgAttr ); + + pBrush->SetWrapMode( + bClamp ? Gdiplus::WrapModeClamp : Gdiplus::WrapModeTile ); + } + + Gdiplus::Matrix aTextureTransform; + tools::gdiPlusMatrixFromAffineMatrix2D( aTextureTransform, + rTexture.AffineTransform ); + + // scale down bitmap to [0,1]x[0,1] rect, as required + // from the XCanvas interface. + pBrush->MultiplyTransform( &aTextureTransform ); + pBrush->ScaleTransform( static_cast< Gdiplus::REAL >(1.0/aBmpSize.Width), + static_cast< Gdiplus::REAL >(1.0/aBmpSize.Height) ); + + // TODO(F1): FillRule + ENSURE_OR_THROW( + Gdiplus::Ok == rGraphics->FillPath( pBrush.get(), + rPath.get() ), + "CanvasHelper::fillTexturedPolyPolygon(): GDI+ call failed" ); + } + } + + + uno::Reference< rendering::XCachedPrimitive > CanvasHelper::fillTexturedPolyPolygon( const rendering::XCanvas* /*pCanvas*/, + const uno::Reference< rendering::XPolyPolygon2D >& xPolyPolygon, + const rendering::ViewState& viewState, + const rendering::RenderState& renderState, + const uno::Sequence< rendering::Texture >& textures ) + { + ENSURE_OR_THROW( xPolyPolygon.is(), + "CanvasHelper::fillTexturedPolyPolygon: polygon is NULL"); + ENSURE_OR_THROW( textures.getLength(), + "CanvasHelper::fillTexturedPolyPolygon: empty texture sequence"); + + if( needOutput() ) + { + GraphicsSharedPtr pGraphics( mpGraphicsProvider->getGraphics() ); + + setupGraphicsState( pGraphics, viewState, renderState ); + + // TODO(F1): Multi-texturing + if( textures[0].Gradient.is() ) + { + // try to cast XParametricPolyPolygon2D reference to + // our implementation class. + ::canvas::ParametricPolyPolygon* pGradient = + dynamic_cast< ::canvas::ParametricPolyPolygon* >( textures[0].Gradient.get() ); + + if( pGradient ) + { + const ::canvas::ParametricPolyPolygon::Values& rValues( + pGradient->getValues() ); + + OSL_ASSERT(rValues.maColors.getLength() == rValues.maStops.getLength() + && rValues.maColors.getLength() > 1); + + std::vector< Gdiplus::Color > aColors(rValues.maColors.getLength()); + std::transform(&rValues.maColors[0], + &rValues.maColors[0]+rValues.maColors.getLength(), + aColors.begin(), + [](const uno::Sequence< double >& aDoubleSequence) { return tools::sequenceToArgb(aDoubleSequence); } ); + std::vector< Gdiplus::REAL > aStops; + comphelper::sequenceToContainer(aStops,rValues.maStops); + + // TODO(E1): Return value + // TODO(F1): FillRule + fillGradient( rValues, + aColors, + aStops, + pGraphics, + tools::graphicsPathFromXPolyPolygon2D( xPolyPolygon ), + viewState, + renderState, + textures[0] ); + } + } + else if( textures[0].Bitmap.is() ) + { + // TODO(E1): Return value + // TODO(F1): FillRule + fillBitmap( textures[0].Bitmap, + pGraphics, + tools::graphicsPathFromXPolyPolygon2D( xPolyPolygon ), + textures[0] ); + } + } + + // TODO(P1): Provide caching here. + return uno::Reference< rendering::XCachedPrimitive >(nullptr); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_config.cxx b/canvas/source/directx/dx_config.cxx new file mode 100644 index 000000000..afb52de30 --- /dev/null +++ b/canvas/source/directx/dx_config.cxx @@ -0,0 +1,152 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> +#include <sal/log.hxx> + +#include <basegfx/vector/b2ivector.hxx> +#include <com/sun/star/uno/Any.hxx> +#include <com/sun/star/uno/Sequence.hxx> +#include <comphelper/anytostring.hxx> +#include <cppuhelper/exc_hlp.hxx> +#include <osl/diagnose.h> +#include <tools/diagnose_ex.h> + +#include "dx_config.hxx" + +using namespace com::sun::star; + +namespace dxcanvas +{ + DXCanvasItem::DXCanvasItem() : + ConfigItem( + "Office.Canvas/DXCanvas", + ConfigItemMode::NONE ), + maValues(), + maMaxTextureSize(), + mbDenylistCurrentDevice(false), + mbValuesDirty(false) + { + try + { + uno::Sequence< uno::Any > aProps( GetProperties( { "DeviceDenylist" } )); + uno::Sequence< sal_Int32 > aValues; + + if( aProps.getLength() > 0 && + (aProps[0] >>= aValues) ) + { + const sal_Int32* pValues = aValues.getConstArray(); + const sal_Int32 nNumEntries( aValues.getLength()*sizeof(sal_Int32)/sizeof(DeviceInfo) ); + for( sal_Int32 i=0; i<nNumEntries; ++i ) + { + DeviceInfo aInfo; + aInfo.nVendorId = *pValues++; + aInfo.nDeviceId = *pValues++; + aInfo.nDeviceSubSysId = *pValues++; + aInfo.nDeviceRevision = *pValues++; + aInfo.nDriverId = *pValues++; + aInfo.nDriverVersion = *pValues++; + aInfo.nDriverSubVersion = *pValues++; + aInfo.nDriverBuildId = *pValues++; + maValues.insert(aInfo); + } + } + + aProps = GetProperties( { "DenylistCurrentDevice" } ); + if( aProps.getLength() > 0 ) + aProps[0] >>= mbDenylistCurrentDevice; + + aProps = GetProperties( { "MaxTextureSize" } ); + if( aProps.getLength() > 0 ) + maMaxTextureSize = aProps[0].get<sal_Int32>(); + else + maMaxTextureSize.reset(); + } + catch( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION( "canvas", "" ); + } + } + + DXCanvasItem::~DXCanvasItem() + { + if( !mbValuesDirty ) + return; + + try + { + uno::Sequence< sal_Int32 > aValues( sizeof(DeviceInfo)/sizeof(sal_Int32)*maValues.size() ); + + sal_Int32* pValues = aValues.getArray(); + for( const auto& rValueSet : maValues ) + { + const DeviceInfo& rInfo( rValueSet ); + *pValues++ = rInfo.nVendorId; + *pValues++ = rInfo.nDeviceId; + *pValues++ = rInfo.nDeviceSubSysId; + *pValues++ = rInfo.nDeviceRevision; + *pValues++ = rInfo.nDriverId; + *pValues++ = rInfo.nDriverVersion; + *pValues++ = rInfo.nDriverSubVersion; + *pValues++ = rInfo.nDriverBuildId; + } + + PutProperties({"DeviceDenylist"}, {css::uno::Any(aValues)}); + } + catch( const uno::Exception& ) + { + TOOLS_WARN_EXCEPTION( "canvas", "" ); + } + } + + void DXCanvasItem::Notify( const css::uno::Sequence<OUString>& ) {} + void DXCanvasItem::ImplCommit() {} + + bool DXCanvasItem::isDeviceUsable( const DeviceInfo& rDeviceInfo ) const + { + return maValues.find(rDeviceInfo) == maValues.end(); + } + + bool DXCanvasItem::isDenylistCurrentDevice() const + { + return mbDenylistCurrentDevice; + } + + void DXCanvasItem::denylistDevice( const DeviceInfo& rDeviceInfo ) + { + mbValuesDirty = true; + maValues.insert(rDeviceInfo); + } + + void DXCanvasItem::adaptMaxTextureSize( basegfx::B2IVector& io_maxTextureSize ) const + { + if( maMaxTextureSize ) + { + io_maxTextureSize.setX( + std::min( *maMaxTextureSize, + io_maxTextureSize.getX() )); + io_maxTextureSize.setY( + std::min( *maMaxTextureSize, + io_maxTextureSize.getY() )); + } + } + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_config.hxx b/canvas/source/directx/dx_config.hxx new file mode 100644 index 000000000..14a77d19d --- /dev/null +++ b/canvas/source/directx/dx_config.hxx @@ -0,0 +1,80 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include <unotools/configitem.hxx> +#include <optional> +#include <set> + +namespace basegfx { class B2IVector; } + +namespace dxcanvas +{ + /** Provide DX canvas config data + */ + class DXCanvasItem : public ::utl::ConfigItem + { + public: + DXCanvasItem(); + + struct DeviceInfo + { + sal_Int32 nVendorId; + sal_Int32 nDeviceId; + sal_Int32 nDeviceSubSysId; + sal_Int32 nDeviceRevision; + + sal_Int32 nDriverId; + sal_Int32 nDriverVersion; + sal_Int32 nDriverSubVersion; + sal_Int32 nDriverBuildId; + + bool operator<( const DeviceInfo& rRHS ) const + { + return nVendorId != rRHS.nVendorId ? nVendorId < rRHS.nVendorId : + (nDeviceId != rRHS.nDeviceId ? nDeviceId < rRHS.nDeviceId : + (nDeviceSubSysId != rRHS.nDeviceSubSysId ? nDeviceSubSysId < rRHS.nDeviceSubSysId : + (nDeviceRevision != rRHS.nDeviceRevision ? nDeviceRevision < rRHS.nDeviceRevision : + (nDriverId != rRHS.nDriverId ? nDriverId < rRHS.nDriverId : + (nDriverVersion != rRHS.nDriverVersion ? nDriverVersion < rRHS.nDriverVersion : + (nDriverSubVersion != rRHS.nDriverSubVersion ? nDriverSubVersion < rRHS.nDriverSubVersion : + (nDriverBuildId != rRHS.nDriverBuildId && nDriverBuildId < rRHS.nDriverBuildId))))))); + } + }; + + ~DXCanvasItem() override; + + bool isDeviceUsable( const DeviceInfo& rDeviceInfo ) const; + bool isDenylistCurrentDevice() const; + void denylistDevice( const DeviceInfo& rDeviceInfo ); + void adaptMaxTextureSize( basegfx::B2IVector& io_maxTextureSize ) const; + virtual void Notify( const css::uno::Sequence<OUString>& aPropertyNames) override; + + private: + virtual void ImplCommit() override; + typedef std::set< DeviceInfo > ValueSet; + ValueSet maValues; + std::optional<sal_Int32> maMaxTextureSize; + bool mbDenylistCurrentDevice; + bool mbValuesDirty; + }; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_devicehelper.cxx b/canvas/source/directx/dx_devicehelper.cxx new file mode 100644 index 000000000..a1b116f99 --- /dev/null +++ b/canvas/source/directx/dx_devicehelper.cxx @@ -0,0 +1,210 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <memory> + +#include <basegfx/utils/canvastools.hxx> +#include <com/sun/star/lang/NoSupportException.hpp> +#include <tools/diagnose_ex.h> +#include <vcl/canvastools.hxx> +#include <vcl/outdev.hxx> +#include <vcl/sysdata.hxx> + +#include <canvas/canvastools.hxx> + +#include "dx_canvasbitmap.hxx" +#include "dx_devicehelper.hxx" +#include "dx_linepolypolygon.hxx" +#include "dx_spritecanvas.hxx" +#include "dx_winstuff.hxx" + +using namespace ::com::sun::star; + +namespace dxcanvas +{ + DeviceHelper::DeviceHelper() : + mpDevice( nullptr ), + mnHDC(nullptr), + mpOutDev(nullptr) + { + } + + DeviceHelper::~DeviceHelper() + { + } + + void DeviceHelper::init( HDC hdc, OutputDevice* pOutDev, + rendering::XGraphicDevice& rDevice ) + { + mnHDC = hdc; + mpDevice = &rDevice; + mpOutDev = pOutDev; + } + + void DeviceHelper::disposing() + { + // release all references + mnHDC = nullptr; + mpDevice = nullptr; + mpOutDev = nullptr; + } + + geometry::RealSize2D DeviceHelper::getPhysicalResolution() + { + if( !mpDevice ) + return ::canvas::tools::createInfiniteSize2D(); // we're disposed + + HDC hDC = getHDC(); + ENSURE_OR_THROW( hDC, + "DeviceHelper::getPhysicalResolution(): cannot retrieve HDC from window" ); + + const int nHorzRes( GetDeviceCaps( hDC, + LOGPIXELSX ) ); + const int nVertRes( GetDeviceCaps( hDC, + LOGPIXELSY ) ); + + return geometry::RealSize2D( nHorzRes*25.4, + nVertRes*25.4 ); + } + + geometry::RealSize2D DeviceHelper::getPhysicalSize() + { + if( !mpDevice ) + return ::canvas::tools::createInfiniteSize2D(); // we're disposed + + HDC hDC=getHDC(); + ENSURE_OR_THROW( hDC, + "DeviceHelper::getPhysicalSize(): cannot retrieve HDC from window" ); + + const int nHorzSize( GetDeviceCaps( hDC, + HORZSIZE ) ); + const int nVertSize( GetDeviceCaps( hDC, + VERTSIZE ) ); + + return geometry::RealSize2D( nHorzSize, + nVertSize ); + } + + uno::Reference< rendering::XLinePolyPolygon2D > DeviceHelper::createCompatibleLinePolyPolygon( + const uno::Reference< rendering::XGraphicDevice >& /*rDevice*/, + const uno::Sequence< uno::Sequence< geometry::RealPoint2D > >& points ) + { + if( !mpDevice ) + return uno::Reference< rendering::XLinePolyPolygon2D >(); // we're disposed + + return uno::Reference< rendering::XLinePolyPolygon2D >( + new LinePolyPolygon( + ::basegfx::unotools::polyPolygonFromPoint2DSequenceSequence( points ) ) ); + } + + uno::Reference< rendering::XBezierPolyPolygon2D > DeviceHelper::createCompatibleBezierPolyPolygon( + const uno::Reference< rendering::XGraphicDevice >& /*rDevice*/, + const uno::Sequence< uno::Sequence< geometry::RealBezierSegment2D > >& points ) + { + if( !mpDevice ) + return uno::Reference< rendering::XBezierPolyPolygon2D >(); // we're disposed + + return uno::Reference< rendering::XBezierPolyPolygon2D >( + new LinePolyPolygon( + ::basegfx::unotools::polyPolygonFromBezier2DSequenceSequence( points ) ) ); + } + + uno::Reference< rendering::XBitmap > DeviceHelper::createCompatibleBitmap( + const uno::Reference< rendering::XGraphicDevice >& /*rDevice*/, + const geometry::IntegerSize2D& size ) + { + if( !mpDevice ) + return uno::Reference< rendering::XBitmap >(); // we're disposed + + DXBitmapSharedPtr pBitmap = std::make_shared<DXBitmap>( + ::basegfx::unotools::b2ISizeFromIntegerSize2D(size), + false); + + // create a 24bit RGB system memory surface + return uno::Reference< rendering::XBitmap >(new CanvasBitmap(pBitmap,mpDevice)); + } + + uno::Reference< rendering::XVolatileBitmap > DeviceHelper::createVolatileBitmap( + const uno::Reference< rendering::XGraphicDevice >& /*rDevice*/, + const geometry::IntegerSize2D& /*size*/ ) + { + return uno::Reference< rendering::XVolatileBitmap >(); + } + + uno::Reference< rendering::XBitmap > DeviceHelper::createCompatibleAlphaBitmap( + const uno::Reference< rendering::XGraphicDevice >& /*rDevice*/, + const geometry::IntegerSize2D& size ) + { + if( !mpDevice ) + return uno::Reference< rendering::XBitmap >(); // we're disposed + + DXBitmapSharedPtr pBitmap = std::make_shared<DXBitmap>( + ::basegfx::unotools::b2ISizeFromIntegerSize2D(size), + true); + + // create a 32bit ARGB system memory surface + return uno::Reference< rendering::XBitmap >(new CanvasBitmap(pBitmap,mpDevice)); + } + + uno::Reference< rendering::XVolatileBitmap > DeviceHelper::createVolatileAlphaBitmap( + const uno::Reference< rendering::XGraphicDevice >& /*rDevice*/, + const geometry::IntegerSize2D& /*size*/ ) + { + return uno::Reference< rendering::XVolatileBitmap >(); + } + + uno::Any DeviceHelper::isAccelerated() const + { + return css::uno::Any(false); + } + + uno::Any DeviceHelper::getDeviceHandle() const + { + return uno::Any( reinterpret_cast< sal_Int64 >(mpOutDev.get()) ); + } + + uno::Any DeviceHelper::getSurfaceHandle() const + { + // TODO(F1): expose DirectDraw object + //return mpBackBuffer->getBitmap().get(); + return uno::Any(); + } + + namespace + { + struct DeviceColorSpace: public rtl::StaticWithInit<uno::Reference<rendering::XColorSpace>, + DeviceColorSpace> + { + uno::Reference<rendering::XColorSpace> operator()() + { + return vcl::unotools::createStandardColorSpace(); + } + }; + } + + uno::Reference<rendering::XColorSpace> DeviceHelper::getColorSpace() const + { + // always the same + return DeviceColorSpace::get(); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_devicehelper.hxx b/canvas/source/directx/dx_devicehelper.hxx new file mode 100644 index 000000000..d5fa62b07 --- /dev/null +++ b/canvas/source/directx/dx_devicehelper.hxx @@ -0,0 +1,114 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include <com/sun/star/awt/Rectangle.hpp> +#include <com/sun/star/rendering/XGraphicDevice.hpp> +#include <com/sun/star/rendering/XBufferController.hpp> + +#include "dx_rendermodule.hxx" +#include "dx_bitmap.hxx" + +#include <rendering/isurfaceproxymanager.hxx> +#include <vcl/vclptr.hxx> + +class OutputDevice; +/* Definition of DeviceHelper class */ + +namespace dxcanvas +{ + class DeviceHelper + { + public: + DeviceHelper(); + ~DeviceHelper(); + + /// make noncopyable + DeviceHelper(const DeviceHelper&) = delete; + const DeviceHelper& operator=(const DeviceHelper&) = delete; + + /** Init the device helper + + @param hdc + private or class dc of the output device. is only stored, + not release + + @param rDevice + Ref back to owning UNO device + */ + void init( HDC hdc, OutputDevice* pOutputDev, + css::rendering::XGraphicDevice& rDevice ); + + /// Dispose all internal references + void disposing(); + + // XWindowGraphicDevice + css::geometry::RealSize2D getPhysicalResolution(); + css::geometry::RealSize2D getPhysicalSize(); + css::uno::Reference< css::rendering::XLinePolyPolygon2D > createCompatibleLinePolyPolygon( + const css::uno::Reference< css::rendering::XGraphicDevice >& rDevice, + const css::uno::Sequence< css::uno::Sequence< css::geometry::RealPoint2D > >& points ); + css::uno::Reference< css::rendering::XBezierPolyPolygon2D > createCompatibleBezierPolyPolygon( + const css::uno::Reference< css::rendering::XGraphicDevice >& rDevice, + const css::uno::Sequence< css::uno::Sequence< css::geometry::RealBezierSegment2D > >& points ); + css::uno::Reference< css::rendering::XBitmap > createCompatibleBitmap( + const css::uno::Reference< css::rendering::XGraphicDevice >& rDevice, + const css::geometry::IntegerSize2D& size ); + css::uno::Reference< css::rendering::XVolatileBitmap > createVolatileBitmap( + const css::uno::Reference< css::rendering::XGraphicDevice >& rDevice, + const css::geometry::IntegerSize2D& size ); + css::uno::Reference< css::rendering::XBitmap > createCompatibleAlphaBitmap( + const css::uno::Reference< css::rendering::XGraphicDevice >& rDevice, + const css::geometry::IntegerSize2D& size ); + css::uno::Reference< css::rendering::XVolatileBitmap > createVolatileAlphaBitmap( + const css::uno::Reference< css::rendering::XGraphicDevice >& rDevice, + const css::geometry::IntegerSize2D& size ); + + css::uno::Any isAccelerated() const; + css::uno::Any getDeviceHandle() const; + css::uno::Any getSurfaceHandle() const; + css::uno::Reference< + css::rendering::XColorSpace > getColorSpace() const; + + /** called when DumpScreenContent property is enabled on + XGraphicDevice, and writes out bitmaps of current screen. + */ + void dumpScreenContent() const {} + + protected: + HDC getHDC() const { return mnHDC; } + css::rendering::XGraphicDevice* getDevice() const { return mpDevice; } + + private: + /** Phyical output device + + Deliberately not a refcounted reference, because of + potential circular references for canvas. Needed to + create bitmaps + */ + css::rendering::XGraphicDevice* mpDevice; + HDC mnHDC; + VclPtr<OutputDevice> mpOutDev; + }; + + typedef ::rtl::Reference< css::rendering::XGraphicDevice > DeviceRef; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_gdiplususer.cxx b/canvas/source/directx/dx_gdiplususer.cxx new file mode 100644 index 000000000..85c143cff --- /dev/null +++ b/canvas/source/directx/dx_gdiplususer.cxx @@ -0,0 +1,74 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <osl/mutex.hxx> + +#include "dx_gdiplususer.hxx" +#include "dx_winstuff.hxx" + + +namespace dxcanvas +{ + namespace + { + ::osl::Mutex* p_gdiPlusUsageCountMutex( osl::Mutex::getGlobalMutex() ); + int n_gdiPlusUsageCount( 0 ); + + ULONG_PTR a_GdiPlusToken; // GDI+ handle. Owned by this object + } + + GDIPlusUser::GDIPlusUserSharedPtr GDIPlusUser::createInstance() + { + return GDIPlusUserSharedPtr( new GDIPlusUser() ); + } + + GDIPlusUser::~GDIPlusUser() + { + ::osl::MutexGuard aGuard( *p_gdiPlusUsageCountMutex ); + + --n_gdiPlusUsageCount; + + if( n_gdiPlusUsageCount == 0 ) + Gdiplus::GdiplusShutdown( a_GdiPlusToken ); + } + + GDIPlusUser::GDIPlusUser() + { + ::osl::MutexGuard aGuard( *p_gdiPlusUsageCountMutex ); + + if( n_gdiPlusUsageCount == 0 ) + { + // Setup GDI+ + + // No extras here, simply taking GdiplusStartupInput's + // default constructor + Gdiplus::GdiplusStartupInput gdiPlusStartupInput; + + Gdiplus::GdiplusStartup( &a_GdiPlusToken, + &gdiPlusStartupInput, + nullptr ); + } + + ++n_gdiPlusUsageCount; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_gdiplususer.hxx b/canvas/source/directx/dx_gdiplususer.hxx new file mode 100644 index 000000000..4b0a0f52a --- /dev/null +++ b/canvas/source/directx/dx_gdiplususer.hxx @@ -0,0 +1,45 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include <sal/config.h> +#include <memory> + +/* Definition of GDIPlusUser class */ + +namespace dxcanvas +{ + class GDIPlusUser + { + public: + typedef std::shared_ptr< GDIPlusUser > GDIPlusUserSharedPtr; + + static GDIPlusUserSharedPtr createInstance(); + ~GDIPlusUser(); + + private: + GDIPlusUser(); // create us via factory method + }; + + typedef GDIPlusUser::GDIPlusUserSharedPtr GDIPlusUserSharedPtr; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_graphicsprovider.hxx b/canvas/source/directx/dx_graphicsprovider.hxx new file mode 100644 index 000000000..2d6d61884 --- /dev/null +++ b/canvas/source/directx/dx_graphicsprovider.hxx @@ -0,0 +1,46 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include "dx_winstuff.hxx" +#include <memory> + +namespace Gdiplus{ class Graphics; } + +namespace dxcanvas +{ + /** Provider of a Gdiplus::Graphics. Interface + */ + class GraphicsProvider + { + public: + GraphicsProvider() = default; + virtual ~GraphicsProvider() {} + /// make noncopyable + GraphicsProvider(const GraphicsProvider&) = delete; + GraphicsProvider& operator=(const GraphicsProvider&) = delete; + + virtual GraphicsSharedPtr getGraphics() = 0; + }; + + typedef std::shared_ptr< GraphicsProvider > GraphicsProviderSharedPtr; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_ibitmap.hxx b/canvas/source/directx/dx_ibitmap.hxx new file mode 100644 index 000000000..6d3f4d6f6 --- /dev/null +++ b/canvas/source/directx/dx_ibitmap.hxx @@ -0,0 +1,61 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include <com/sun/star/rendering/XCanvas.hpp> +#include <com/sun/star/rendering/XIntegerBitmap.hpp> +#include <basegfx/vector/b2ivector.hxx> +#include <basegfx/point/b2dpoint.hxx> +#include <basegfx/range/b2drange.hxx> +#include <memory> +#include "dx_graphicsprovider.hxx" + +namespace dxcanvas +{ + /// Interface for internal canvas bitmap objects + struct IBitmap : public GraphicsProvider + { + virtual BitmapSharedPtr getBitmap() const = 0; + virtual ::basegfx::B2IVector getSize() const = 0; + virtual bool hasAlpha() const = 0; + + virtual css::uno::Sequence< sal_Int8 > getData( + css::rendering::IntegerBitmapLayout& bitmapLayout, + const css::geometry::IntegerRectangle2D& rect ) = 0; + + virtual void setData( + const css::uno::Sequence< sal_Int8 >& data, + const css::rendering::IntegerBitmapLayout& bitmapLayout, + const css::geometry::IntegerRectangle2D& rect ) = 0; + + virtual void setPixel( + const css::uno::Sequence< sal_Int8 >& color, + const css::rendering::IntegerBitmapLayout& bitmapLayout, + const css::geometry::IntegerPoint2D& pos ) = 0; + + virtual css::uno::Sequence< sal_Int8 > getPixel( + css::rendering::IntegerBitmapLayout& bitmapLayout, + const css::geometry::IntegerPoint2D& pos ) = 0; + }; + + typedef std::shared_ptr<IBitmap> IBitmapSharedPtr; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_impltools.cxx b/canvas/source/directx/dx_impltools.cxx new file mode 100644 index 000000000..7f464a851 --- /dev/null +++ b/canvas/source/directx/dx_impltools.cxx @@ -0,0 +1,629 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <algorithm> +#include <memory> +#include <vector> + + +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/numeric/ftools.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/polygon/b2dpolypolygon.hxx> +#include <basegfx/range/b2drectangle.hxx> +#include <basegfx/range/b2irectangle.hxx> +#include <basegfx/utils/canvastools.hxx> +#include <com/sun/star/geometry/IntegerRectangle2D.hpp> +#include <com/sun/star/geometry/RealPoint2D.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/lang/XUnoTunnel.hpp> +#include <tools/diagnose_ex.h> + +#include <canvas/canvastools.hxx> +#include <verifyinput.hxx> + +#include "dx_canvas.hxx" +#include "dx_canvasbitmap.hxx" +#include "dx_canvasfont.hxx" +#include "dx_impltools.hxx" +#include "dx_linepolypolygon.hxx" +#include "dx_spritecanvas.hxx" +#include "dx_vcltools.hxx" + + +using namespace ::com::sun::star; + + +namespace dxcanvas::tools +{ + ::basegfx::B2DPolyPolygon polyPolygonFromXPolyPolygon2D( const uno::Reference< rendering::XPolyPolygon2D >& xPoly ) + { + LinePolyPolygon* pPolyImpl = dynamic_cast< LinePolyPolygon* >( xPoly.get() ); + + if( pPolyImpl ) + { + return pPolyImpl->getPolyPolygon(); + } + else + { + const sal_Int32 nPolys( xPoly->getNumberOfPolygons() ); + + // not a known implementation object - try data source + // interfaces + uno::Reference< rendering::XBezierPolyPolygon2D > xBezierPoly( + xPoly, + uno::UNO_QUERY ); + + if( xBezierPoly.is() ) + { + return ::basegfx::unotools::polyPolygonFromBezier2DSequenceSequence( + xBezierPoly->getBezierSegments( 0, + nPolys, + 0, + -1 ) ); + } + else + { + uno::Reference< rendering::XLinePolyPolygon2D > xLinePoly( + xPoly, + uno::UNO_QUERY ); + + // no implementation class and no data provider + // found - contract violation. + ENSURE_ARG_OR_THROW( xLinePoly.is(), + "VCLCanvas::polyPolygonFromXPolyPolygon2D(): Invalid input " + "poly-polygon, cannot retrieve vertex data" ); + + return ::basegfx::unotools::polyPolygonFromPoint2DSequenceSequence( + xLinePoly->getPoints( 0, + nPolys, + 0, + -1 ) ); + } + } + } + + void setupGraphics( Gdiplus::Graphics& rGraphics ) + { + // setup graphics with (somewhat arbitrary) defaults + //rGraphics.SetCompositingQuality( Gdiplus::CompositingQualityHighQuality ); + rGraphics.SetCompositingQuality( Gdiplus::CompositingQualityHighSpeed ); + //rGraphics.SetInterpolationMode( Gdiplus::InterpolationModeHighQualityBilinear ); // with prefiltering for shrinks + rGraphics.SetInterpolationMode( Gdiplus::InterpolationModeBilinear ); + + // #122683# Switched precedence of pixel offset + // mode. Seemingly, polygon stroking needs + // PixelOffsetModeNone to achieve visually pleasing + // results, whereas all other operations (e.g. polygon + // fills, bitmaps) look better with PixelOffsetModeHalf. + rGraphics.SetPixelOffsetMode( Gdiplus::PixelOffsetModeHalf ); // Pixel center at (0.5, 0.5) etc. + //rGraphics.SetPixelOffsetMode( Gdiplus::PixelOffsetModeNone ); + + //rGraphics.SetSmoothingMode( Gdiplus::SmoothingModeHighSpeed ); // no line/curve antialiasing + //rGraphics.SetSmoothingMode( Gdiplus::SmoothingModeHighQuality ); + rGraphics.SetSmoothingMode( Gdiplus::SmoothingModeAntiAlias ); + //rGraphics.SetTextRenderingHint( Gdiplus::TextRenderingHintAntiAlias ); + rGraphics.SetTextRenderingHint( Gdiplus::TextRenderingHintSystemDefault ); + rGraphics.SetPageUnit(Gdiplus::UnitPixel); + } + + Gdiplus::Graphics* createGraphicsFromHDC(HDC aHDC) + { + Gdiplus::Graphics* pRet = new Gdiplus::Graphics(aHDC); + setupGraphics( *pRet ); + return pRet; + } + + GraphicsSharedPtr createGraphicsFromBitmap(const BitmapSharedPtr& rBitmap) + { + GraphicsSharedPtr pRet(Gdiplus::Graphics::FromImage(rBitmap.get())); + if( pRet ) + setupGraphics( *pRet ); + return pRet; + } + + void gdiPlusMatrixFromB2DHomMatrix( Gdiplus::Matrix& rGdiplusMatrix, const ::basegfx::B2DHomMatrix& rMatrix ) + { + rGdiplusMatrix.SetElements( static_cast<Gdiplus::REAL>(rMatrix.get(0,0)), + static_cast<Gdiplus::REAL>(rMatrix.get(1,0)), + static_cast<Gdiplus::REAL>(rMatrix.get(0,1)), + static_cast<Gdiplus::REAL>(rMatrix.get(1,1)), + static_cast<Gdiplus::REAL>(rMatrix.get(0,2)), + static_cast<Gdiplus::REAL>(rMatrix.get(1,2)) ); + } + + void gdiPlusMatrixFromAffineMatrix2D( Gdiplus::Matrix& rGdiplusMatrix, + const geometry::AffineMatrix2D& rMatrix ) + { + rGdiplusMatrix.SetElements( static_cast<Gdiplus::REAL>(rMatrix.m00), + static_cast<Gdiplus::REAL>(rMatrix.m10), + static_cast<Gdiplus::REAL>(rMatrix.m01), + static_cast<Gdiplus::REAL>(rMatrix.m11), + static_cast<Gdiplus::REAL>(rMatrix.m02), + static_cast<Gdiplus::REAL>(rMatrix.m12) ); + } + + namespace + { + // TODO(P2): Check whether this gets inlined. If not, make functor + // out of it + Gdiplus::PointF implGdiPlusPointFromRealPoint2D( const css::geometry::RealPoint2D& rPoint ) + { + return Gdiplus::PointF( static_cast<Gdiplus::REAL>(rPoint.X), + static_cast<Gdiplus::REAL>(rPoint.Y) ); + } + + void graphicsPathFromB2DPolygon( GraphicsPathSharedPtr const & rOutput, + std::vector< Gdiplus::PointF >& rPoints, + const ::basegfx::B2DPolygon& rPoly, + bool bNoLineJoin) + { + const sal_uInt32 nPoints( rPoly.count() ); + + if( nPoints < 2 ) + return; + + rOutput->StartFigure(); + + const bool bClosedPolygon( rPoly.isClosed() ); + + if( rPoly.areControlPointsUsed() ) + { + // control points used -> for now, add all + // segments as curves to GraphicsPath + + // If the polygon is closed, we need to add the + // first point, thus, one more (can't simply + // GraphicsPath::CloseFigure() it, since the last + // point cannot have any control points for GDI+) + rPoints.resize( 3*nPoints + (bClosedPolygon ? 1 : 0) ); + + sal_uInt32 nCurrOutput=0; + for( sal_uInt32 nCurrPoint=0; nCurrPoint<nPoints; ++nCurrPoint ) + { + const ::basegfx::B2DPoint& rPoint( rPoly.getB2DPoint( nCurrPoint ) ); + rPoints[nCurrOutput++] = Gdiplus::PointF( static_cast<Gdiplus::REAL>(rPoint.getX()), + static_cast<Gdiplus::REAL>(rPoint.getY()) ); + + const ::basegfx::B2DPoint& rControlPointA( rPoly.getNextControlPoint( nCurrPoint ) ); + rPoints[nCurrOutput++] = Gdiplus::PointF( static_cast<Gdiplus::REAL>(rControlPointA.getX()), + static_cast<Gdiplus::REAL>(rControlPointA.getY()) ); + + const ::basegfx::B2DPoint& rControlPointB( rPoly.getPrevControlPoint( (nCurrPoint + 1) % nPoints) ); + rPoints[nCurrOutput++] = Gdiplus::PointF( static_cast<Gdiplus::REAL>(rControlPointB.getX()), + static_cast<Gdiplus::REAL>(rControlPointB.getY()) ); + } + + if( bClosedPolygon ) + { + // add first point again (to be able to pass + // control points for the last point, see + // above) + const ::basegfx::B2DPoint& rPoint( rPoly.getB2DPoint(0) ); + rPoints[nCurrOutput++] = Gdiplus::PointF( static_cast<Gdiplus::REAL>(rPoint.getX()), + static_cast<Gdiplus::REAL>(rPoint.getY()) ); + + if(bNoLineJoin && nCurrOutput > 7) + { + for(sal_uInt32 a(3); a < nCurrOutput; a+=3) + { + rOutput->StartFigure(); + rOutput->AddBezier(rPoints[a - 3], rPoints[a - 2], rPoints[a - 1], rPoints[a]); + } + } + else + { + rOutput->AddBeziers( rPoints.data(), nCurrOutput ); + } + } + else + { + // GraphicsPath expects 3(n-1)+1 points (i.e. the + // last point must not have any trailing control + // points after it). + // Therefore, simply don't pass the last two + // points here. + if( nCurrOutput > 3 ) + { + if(bNoLineJoin && nCurrOutput > 7) + { + for(sal_uInt32 a(3); a < nCurrOutput; a+=3) + { + rOutput->StartFigure(); + rOutput->AddBezier(rPoints[a - 3], rPoints[a - 2], rPoints[a - 1], rPoints[a]); + } + } + else + { + rOutput->AddBeziers( rPoints.data(), nCurrOutput-2 ); + } + } + } + } + else + { + // no control points -> no curves, simply add + // straight lines to GraphicsPath + rPoints.resize( nPoints ); + + for( sal_uInt32 nCurrPoint=0; nCurrPoint<nPoints; ++nCurrPoint ) + { + const ::basegfx::B2DPoint& rPoint( rPoly.getB2DPoint( nCurrPoint ) ); + rPoints[nCurrPoint] = Gdiplus::PointF( static_cast<Gdiplus::REAL>(rPoint.getX()), + static_cast<Gdiplus::REAL>(rPoint.getY()) ); + } + + if(bNoLineJoin && nPoints > 2) + { + for(sal_uInt32 a(1); a < nPoints; a++) + { + rOutput->StartFigure(); + rOutput->AddLine(rPoints[a - 1], rPoints[a]); + } + + if(bClosedPolygon) + { + rOutput->StartFigure(); + rOutput->AddLine(rPoints[nPoints - 1], rPoints[0]); + } + } + else + { + rOutput->AddLines( rPoints.data(), nPoints ); + } + } + + if( bClosedPolygon && !bNoLineJoin ) + rOutput->CloseFigure(); + } + } + + Gdiplus::Rect gdiPlusRectFromIntegerRectangle2D( const geometry::IntegerRectangle2D& rRect ) + { + return Gdiplus::Rect( rRect.X1, + rRect.Y1, + rRect.X2 - rRect.X1, + rRect.Y2 - rRect.Y1 ); + } + + Gdiplus::RectF gdiPlusRectFFromRectangle2D( const geometry::RealRectangle2D& rRect ) + { + return Gdiplus::RectF( static_cast<Gdiplus::REAL>(rRect.X1), + static_cast<Gdiplus::REAL>(rRect.Y1), + static_cast<Gdiplus::REAL>(rRect.X2 - rRect.X1), + static_cast<Gdiplus::REAL>(rRect.Y2 - rRect.Y1) ); + } + + RECT gdiRectFromB2IRect( const ::basegfx::B2IRange& rRect ) + { + RECT aRect = {rRect.getMinX(), + rRect.getMinY(), + rRect.getMaxX(), + rRect.getMaxY()}; + + return aRect; + } + + geometry::RealPoint2D realPoint2DFromGdiPlusPointF( const Gdiplus::PointF& rPoint ) + { + return geometry::RealPoint2D( rPoint.X, rPoint.Y ); + } + + geometry::RealRectangle2D realRectangle2DFromGdiPlusRectF( const Gdiplus::RectF& rRect ) + { + return geometry::RealRectangle2D( rRect.X, rRect.Y, + rRect.X + rRect.Width, + rRect.Y + rRect.Height ); + } + + ::basegfx::B2DPoint b2dPointFromGdiPlusPointF( const Gdiplus::PointF& rPoint ) + { + return ::basegfx::B2DPoint( rPoint.X, rPoint.Y ); + } + + ::basegfx::B2DRange b2dRangeFromGdiPlusRectF( const Gdiplus::RectF& rRect ) + { + return ::basegfx::B2DRange( rRect.X, rRect.Y, + rRect.X + rRect.Width, + rRect.Y + rRect.Height ); + } + + uno::Sequence< sal_Int8 > argbToIntSequence( Gdiplus::ARGB rColor ) + { + // TODO(F1): handle color space conversions, when defined on canvas/graphicDevice + return + { + static_cast<sal_Int8>((rColor >> 16) & 0xFF), // red + static_cast<sal_Int8>((rColor >> 8) & 0xFF), // green + static_cast<sal_Int8>(rColor & 0xFF), // blue + static_cast<sal_Int8>((rColor >> 24) & 0xFF) // alpha + }; + } + + Gdiplus::ARGB sequenceToArgb( const uno::Sequence< sal_Int8 >& rColor ) + { + ENSURE_OR_THROW( rColor.getLength() > 2, + "sequenceToArgb: need at least three channels" ); + + // TODO(F1): handle color space conversions, when defined on canvas/graphicDevice + Gdiplus::ARGB aColor; + + aColor = (static_cast<sal_uInt8>(rColor[0]) << 16) | (static_cast<sal_uInt8>(rColor[1]) << 8) | static_cast<sal_uInt8>(rColor[2]); + + if( rColor.getLength() > 3 ) + aColor |= static_cast<sal_uInt8>(rColor[3]) << 24; + + return aColor; + } + + Gdiplus::ARGB sequenceToArgb( const uno::Sequence< double >& rColor ) + { + ENSURE_OR_THROW( rColor.getLength() > 2, + "sequenceToColor: need at least three channels" ); + + // TODO(F1): handle color space conversions, when defined on canvas/graphicDevice + Gdiplus::ARGB aColor; + + ::canvas::tools::verifyRange(rColor[0],0.0,1.0); + ::canvas::tools::verifyRange(rColor[1],0.0,1.0); + ::canvas::tools::verifyRange(rColor[2],0.0,1.0); + + aColor = + (static_cast<sal_uInt8>( ::basegfx::fround( 255*rColor[0] ) ) << 16) | + (static_cast<sal_uInt8>( ::basegfx::fround( 255*rColor[1] ) ) << 8) | + static_cast<sal_uInt8>( ::basegfx::fround( 255*rColor[2] ) ); + + if( rColor.getLength() > 3 ) + { + ::canvas::tools::verifyRange(rColor[3],0.0,1.0); + aColor |= static_cast<sal_uInt8>( ::basegfx::fround( 255*rColor[3] ) ) << 24; + } + + return aColor; + } + + GraphicsPathSharedPtr graphicsPathFromRealPoint2DSequence( const uno::Sequence< uno::Sequence< geometry::RealPoint2D > >& points ) + { + GraphicsPathSharedPtr pRes = std::make_shared<Gdiplus::GraphicsPath>(); + std::vector< Gdiplus::PointF > aPoints; + + for( uno::Sequence< geometry::RealPoint2D > const & seqPoints : points ) + { + const sal_Int32 nCurrSize( seqPoints.getLength() ); + if( nCurrSize ) + { + aPoints.resize( nCurrSize ); + + // TODO(F1): Closed/open polygons + + // convert from RealPoint2D array to Gdiplus::PointF array + std::transform( seqPoints.getConstArray(), + seqPoints.getConstArray()+nCurrSize, + aPoints.begin(), + implGdiPlusPointFromRealPoint2D ); + + pRes->AddLines( aPoints.data(), nCurrSize ); + } + } + + return pRes; + } + + GraphicsPathSharedPtr graphicsPathFromB2DPolygon( const ::basegfx::B2DPolygon& rPoly, bool bNoLineJoin ) + { + GraphicsPathSharedPtr pRes = std::make_shared<Gdiplus::GraphicsPath>(); + std::vector< Gdiplus::PointF > aPoints; + + graphicsPathFromB2DPolygon( pRes, aPoints, rPoly, bNoLineJoin ); + + return pRes; + } + + GraphicsPathSharedPtr graphicsPathFromB2DPolyPolygon( const ::basegfx::B2DPolyPolygon& rPoly, bool bNoLineJoin ) + { + GraphicsPathSharedPtr pRes = std::make_shared<Gdiplus::GraphicsPath>(); + std::vector< Gdiplus::PointF > aPoints; + + const sal_uInt32 nPolies( rPoly.count() ); + for( sal_uInt32 nCurrPoly=0; nCurrPoly<nPolies; ++nCurrPoly ) + { + graphicsPathFromB2DPolygon( pRes, + aPoints, + rPoly.getB2DPolygon( nCurrPoly ), + bNoLineJoin); + } + + return pRes; + } + + GraphicsPathSharedPtr graphicsPathFromXPolyPolygon2D( const uno::Reference< rendering::XPolyPolygon2D >& xPoly, bool bNoLineJoin ) + { + LinePolyPolygon* pPolyImpl = dynamic_cast< LinePolyPolygon* >( xPoly.get() ); + + if( pPolyImpl ) + { + return pPolyImpl->getGraphicsPath( bNoLineJoin ); + } + else + { + return tools::graphicsPathFromB2DPolyPolygon( + polyPolygonFromXPolyPolygon2D( xPoly ), bNoLineJoin ); + } + } + + bool drawGdiPlusBitmap( const GraphicsSharedPtr& rGraphics, + const BitmapSharedPtr& rBitmap ) + { + Gdiplus::PointF aPoint; + return (Gdiplus::Ok == rGraphics->DrawImage( rBitmap.get(), + aPoint ) ); + } + + bool drawDIBits( const std::shared_ptr<Gdiplus::Graphics>& rGraphics, + const BITMAPINFO& rBI, + const void* pBits ) + { + BitmapSharedPtr pBitmap( + Gdiplus::Bitmap::FromBITMAPINFO( &rBI, + const_cast<void*>(pBits) ) ); + + return drawGdiPlusBitmap( rGraphics, + pBitmap ); + } + + bool drawRGBABits( const std::shared_ptr<Gdiplus::Graphics>& rGraphics, + const RawRGBABitmap& rRawRGBAData ) + { + BitmapSharedPtr pBitmap = std::make_shared<Gdiplus::Bitmap>( rRawRGBAData.mnWidth, + rRawRGBAData.mnHeight, + PixelFormat32bppARGB ); + + Gdiplus::BitmapData aBmpData; + aBmpData.Width = rRawRGBAData.mnWidth; + aBmpData.Height = rRawRGBAData.mnHeight; + aBmpData.Stride = 4*aBmpData.Width; // bottom-up format + aBmpData.PixelFormat = PixelFormat32bppARGB; + aBmpData.Scan0 = const_cast<sal_uInt8*>(rRawRGBAData.maBitmapData.data()); + + const Gdiplus::Rect aRect( 0,0,aBmpData.Width,aBmpData.Height ); + if( Gdiplus::Ok != pBitmap->LockBits( &aRect, + Gdiplus::ImageLockModeWrite | Gdiplus::ImageLockModeUserInputBuf, + PixelFormat32bppARGB, + &aBmpData ) ) + { + return false; + } + + // commit data to bitmap + pBitmap->UnlockBits( &aBmpData ); + + return drawGdiPlusBitmap( rGraphics, + pBitmap ); + } + + BitmapSharedPtr bitmapFromXBitmap( const uno::Reference< rendering::XBitmap >& xBitmap ) + { + BitmapProvider* pBitmapProvider = dynamic_cast< BitmapProvider* >(xBitmap.get()); + + if( pBitmapProvider ) + { + IBitmapSharedPtr pBitmap( pBitmapProvider->getBitmap() ); + return pBitmap->getBitmap(); + } + else + { + // not a native CanvasBitmap, extract VCL bitmap and + // render into GDI+ bitmap of similar size + // ================================================= + + const geometry::IntegerSize2D aBmpSize( xBitmap->getSize() ); + BitmapSharedPtr pBitmap; + + if( xBitmap->hasAlpha() ) + { + // TODO(P2): At least for the alpha bitmap case, it + // would be possible to generate the corresponding + // bitmap directly + pBitmap = std::make_shared<Gdiplus::Bitmap>( aBmpSize.Width, + aBmpSize.Height, + PixelFormat32bppARGB ); + } + else + { + // TODO(F2): Might be wise to create bitmap compatible + // to the VCL bitmap. Also, check whether the VCL + // bitmap's system handles can be used to create the + // GDI+ bitmap (currently, it does not seem so). + pBitmap = std::make_shared<Gdiplus::Bitmap>( aBmpSize.Width, + aBmpSize.Height, + PixelFormat24bppRGB ); + } + + GraphicsSharedPtr pGraphics(createGraphicsFromBitmap(pBitmap)); + tools::setupGraphics(*pGraphics); + if( !drawVCLBitmapFromXBitmap( + pGraphics, + xBitmap) ) + { + pBitmap.reset(); + } + + return pBitmap; + } + } + + CanvasFont::ImplRef canvasFontFromXFont( const uno::Reference< rendering::XCanvasFont >& xFont ) + { + CanvasFont* pCanvasFont = dynamic_cast< CanvasFont* >(xFont.get()); + + ENSURE_ARG_OR_THROW( pCanvasFont, + "canvasFontFromXFont(): Invalid XFont (or incompatible font for this XCanvas)" ); + + return CanvasFont::ImplRef( pCanvasFont ); + } + + void setModulateImageAttributes( Gdiplus::ImageAttributes& o_rAttr, + double nRedModulation, + double nGreenModulation, + double nBlueModulation, + double nAlphaModulation ) + { + // This gets rather verbose, but we have to setup a color + // transformation matrix, in order to incorporate the global + // alpha value mfAlpha into the bitmap rendering. + Gdiplus::ColorMatrix aColorMatrix; + + aColorMatrix.m[0][0] = static_cast<Gdiplus::REAL>(nRedModulation); + aColorMatrix.m[0][1] = 0.0; + aColorMatrix.m[0][2] = 0.0; + aColorMatrix.m[0][3] = 0.0; + aColorMatrix.m[0][4] = 0.0; + + aColorMatrix.m[1][0] = 0.0; + aColorMatrix.m[1][1] = static_cast<Gdiplus::REAL>(nGreenModulation); + aColorMatrix.m[1][2] = 0.0; + aColorMatrix.m[1][3] = 0.0; + aColorMatrix.m[1][4] = 0.0; + + aColorMatrix.m[2][0] = 0.0; + aColorMatrix.m[2][1] = 0.0; + aColorMatrix.m[2][2] = static_cast<Gdiplus::REAL>(nBlueModulation); + aColorMatrix.m[2][3] = 0.0; + aColorMatrix.m[2][4] = 0.0; + + aColorMatrix.m[3][0] = 0.0; + aColorMatrix.m[3][1] = 0.0; + aColorMatrix.m[3][2] = 0.0; + aColorMatrix.m[3][3] = static_cast<Gdiplus::REAL>(nAlphaModulation); + aColorMatrix.m[3][4] = 0.0; + + aColorMatrix.m[4][0] = 0.0; + aColorMatrix.m[4][1] = 0.0; + aColorMatrix.m[4][2] = 0.0; + aColorMatrix.m[4][3] = 0.0; + aColorMatrix.m[4][4] = 1.0; + + o_rAttr.SetColorMatrix( &aColorMatrix ); + } + +} // namespace dxcanvas::tools + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_impltools.hxx b/canvas/source/directx/dx_impltools.hxx new file mode 100644 index 000000000..2c6b85ce8 --- /dev/null +++ b/canvas/source/directx/dx_impltools.hxx @@ -0,0 +1,124 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/uno/Sequence.hxx> +#include <com/sun/star/util/TriState.hpp> + +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/polygon/b2dpolypolygon.hxx> +#include <basegfx/numeric/ftools.hxx> + +#include <memory> +#include "dx_canvasfont.hxx" + +namespace basegfx +{ + class B2DPoint; + class B2DRange; + class B2DHomMatrix; + class B2IRange; + class B2DPolyPolygon; +}; + +namespace com::sun::star::geometry +{ + struct IntegerRectangle2D; + struct RealPoint2D; +} + +namespace com::sun::star::rendering +{ + class XBitmap; + class XPolyPolygon2D; + class XCanvasFont; +} + + +namespace dxcanvas::tools +{ + struct RawRGBABitmap; + + ::basegfx::B2DPolyPolygon + polyPolygonFromXPolyPolygon2D( const css::uno::Reference< css::rendering::XPolyPolygon2D >& ); + + Gdiplus::Graphics* createGraphicsFromHDC(HDC); + GraphicsSharedPtr createGraphicsFromBitmap(const BitmapSharedPtr&); + + void setupGraphics( Gdiplus::Graphics& rGraphics ); + + void gdiPlusMatrixFromB2DHomMatrix( Gdiplus::Matrix& rGdiplusMatrix, + const ::basegfx::B2DHomMatrix& rMatrix ); + void gdiPlusMatrixFromAffineMatrix2D( Gdiplus::Matrix& rGdiplusMatrix, + const css::geometry::AffineMatrix2D& rMatrix ); + + Gdiplus::PointF gdiPlusPointFFromRealPoint2D( const css::geometry::RealPoint2D& ); + Gdiplus::RectF gdiPlusRectFFromRectangle2D( const css::geometry::RealRectangle2D& ); + Gdiplus::Rect gdiPlusRectFromIntegerRectangle2D( const css::geometry::IntegerRectangle2D& ); + RECT gdiRectFromB2IRect( const ::basegfx::B2IRange& ); + + css::geometry::RealPoint2D realPoint2DFromGdiPlusPointF( const Gdiplus::PointF& ); + css::geometry::RealRectangle2D realRectangle2DFromGdiPlusRectF( const Gdiplus::RectF& ); + + ::basegfx::B2DPoint b2dPointFromGdiPlusPointF( const Gdiplus::PointF& ); + ::basegfx::B2DRange b2dRangeFromGdiPlusRectF( const Gdiplus::RectF& ); + + css::uno::Sequence< sal_Int8 > argbToIntSequence( Gdiplus::ARGB rColor ); + Gdiplus::ARGB sequenceToArgb( const css::uno::Sequence< sal_Int8 >& rColor ); + Gdiplus::ARGB sequenceToArgb( const css::uno::Sequence< double >& rColor ); + + GraphicsPathSharedPtr graphicsPathFromRealPoint2DSequence( const css::uno::Sequence< + css::uno::Sequence< css::geometry::RealPoint2D > >& ); + + GraphicsPathSharedPtr graphicsPathFromB2DPolygon( + const ::basegfx::B2DPolygon& rPoly, + bool bNoLineJoin = false); + + GraphicsPathSharedPtr graphicsPathFromB2DPolyPolygon( + const ::basegfx::B2DPolyPolygon& rPoly, + bool bNoLineJoin = false); + + GraphicsPathSharedPtr graphicsPathFromXPolyPolygon2D( + const css::uno::Reference< css::rendering::XPolyPolygon2D >&, + bool bNoLineJoin = false ); + + bool drawGdiPlusBitmap( const GraphicsSharedPtr& rGraphics, + const BitmapSharedPtr& rBitmap ); + bool drawDIBits( const std::shared_ptr< Gdiplus::Graphics >& rGraphics, + const BITMAPINFO& rBI, + const void* pBits ); + + bool drawRGBABits( const std::shared_ptr< Gdiplus::Graphics >& rGraphics, + const RawRGBABitmap& rRawRGBAData ); + + BitmapSharedPtr bitmapFromXBitmap( const css::uno::Reference< css::rendering::XBitmap >& xBitmap ); + + CanvasFont::ImplRef canvasFontFromXFont( const css::uno::Reference< css::rendering::XCanvasFont >& xFont ); + + void setModulateImageAttributes( Gdiplus::ImageAttributes& o_rAttr, + double nRedModulation, + double nGreenModulation, + double nBlueModulation, + double nAlphaModulation ); +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_linepolypolygon.cxx b/canvas/source/directx/dx_linepolypolygon.cxx new file mode 100644 index 000000000..53ec2953e --- /dev/null +++ b/canvas/source/directx/dx_linepolypolygon.cxx @@ -0,0 +1,59 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <basegfx/utils/canvastools.hxx> + +#include "dx_linepolypolygon.hxx" + + +using namespace ::com::sun::star; + +namespace dxcanvas +{ + LinePolyPolygon::LinePolyPolygon( const ::basegfx::B2DPolyPolygon& rPolyPoly ) : + ::basegfx::unotools::UnoPolyPolygon( rPolyPoly ), + mpGdiPlusUser( GDIPlusUser::createInstance() ), + mpPath() + { + } + + GraphicsPathSharedPtr LinePolyPolygon::getGraphicsPath( bool bNoLineJoin ) const + { + // generate GraphicsPath only on demand (gets deleted as soon + // as any of the modifying methods above touches the + // B2DPolyPolygon). + if( !mpPath ) + { + mpPath = tools::graphicsPathFromB2DPolyPolygon( getPolyPolygonUnsafe(), bNoLineJoin ); + mpPath->SetFillMode( const_cast<LinePolyPolygon*>(this)->getFillRule() == rendering::FillRule_EVEN_ODD ? + Gdiplus::FillModeAlternate : Gdiplus::FillModeWinding ); + } + + return mpPath; + } + + void LinePolyPolygon::modifying() const + { + mpPath.reset(); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_linepolypolygon.hxx b/canvas/source/directx/dx_linepolypolygon.hxx new file mode 100644 index 000000000..eaec483a0 --- /dev/null +++ b/canvas/source/directx/dx_linepolypolygon.hxx @@ -0,0 +1,47 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include <canvas/canvastools.hxx> +#include <basegfx/utils/unopolypolygon.hxx> + +#include "dx_gdiplususer.hxx" +#include "dx_impltools.hxx" + + +namespace dxcanvas +{ + class LinePolyPolygon : public ::basegfx::unotools::UnoPolyPolygon + { + public: + explicit LinePolyPolygon( const ::basegfx::B2DPolyPolygon& ); + + GraphicsPathSharedPtr getGraphicsPath( bool bNoLineJoin = false) const; + + private: + // overridden, to clear mpPath + virtual void modifying() const override; + + GDIPlusUserSharedPtr mpGdiPlusUser; + mutable GraphicsPathSharedPtr mpPath; + }; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_rendermodule.hxx b/canvas/source/directx/dx_rendermodule.hxx new file mode 100644 index 000000000..6e8a8fb2a --- /dev/null +++ b/canvas/source/directx/dx_rendermodule.hxx @@ -0,0 +1,80 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include <basegfx/vector/b2ivector.hxx> +#include <basegfx/range/b2irectangle.hxx> +#include <rendering/irendermodule.hxx> +#include <memory> +#include "dx_winstuff.hxx" + +namespace vcl { class Window; } +namespace basegfx +{ + class B2IRange; +} + +namespace dxcanvas +{ + /// Specialization of IRenderModule for DirectX + struct IDXRenderModule : public canvas::IRenderModule + { + /** Flip front- and backbuffer, update only given area + + Note: Both update area and offset are ignored for + fullscreen canvas, that uses page flipping (cannot, by + definition, do anything else there except displaying the + full backbuffer instead of the front buffer) + + @param rUpdateArea + Area to copy from backbuffer to front + + @param rCurrWindowArea + Current area of VCL window (coordinates relative to VCL + HWND) + */ + virtual bool flip( const ::basegfx::B2IRectangle& rUpdateArea, + const ::basegfx::B2IRectangle& rCurrWindowArea ) = 0; + + /** Resize backbuffer area for this render module + */ + virtual void resize( const ::basegfx::B2IRange& rect ) = 0; + + /// Write a snapshot of the screen to disk + virtual void screenShot() = 0; + + virtual sal::systools::COMReference<surface_type> + createSystemMemorySurface( + const ::basegfx::B2IVector& rSize ) = 0; + + virtual void disposing() = 0; + virtual HWND getHWND() const = 0; + }; + + typedef std::shared_ptr< IDXRenderModule > IDXRenderModuleSharedPtr; + + + /** Factory method, to create an IRenderModule instance for the + given VCL window instance + */ + IDXRenderModuleSharedPtr createRenderModule( const vcl::Window& rParent ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_sprite.hxx b/canvas/source/directx/dx_sprite.hxx new file mode 100644 index 000000000..c1f75e1b1 --- /dev/null +++ b/canvas/source/directx/dx_sprite.hxx @@ -0,0 +1,45 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include <base/sprite.hxx> + +namespace dxcanvas +{ + /** Specialization of ::canvas::Sprite interface, to also provide + redraw methods. + */ + class Sprite : public ::canvas::Sprite + { + public: + + /** Redraw sprite using the hardware + + This method will silently fail, if the previous + restoreTextures() call failed. + */ + virtual void redraw() const = 0; + + protected: + ~Sprite() {} + }; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_spritecanvas.cxx b/canvas/source/directx/dx_spritecanvas.cxx new file mode 100644 index 000000000..0a16f2ae9 --- /dev/null +++ b/canvas/source/directx/dx_spritecanvas.cxx @@ -0,0 +1,193 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> +#include <sal/log.hxx> + +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/numeric/ftools.hxx> +#include <basegfx/point/b2dpoint.hxx> +#include <basegfx/utils/canvastools.hxx> +#include <com/sun/star/lang/NoSupportException.hpp> +#include <com/sun/star/lang/XSingleServiceFactory.hpp> +#include <com/sun/star/registry/XRegistryKey.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <cppuhelper/supportsservice.hxx> +#include <osl/mutex.hxx> +#include <toolkit/helper/vclunohelper.hxx> +#include <tools/diagnose_ex.h> + +#include <canvas/canvastools.hxx> + +#include "dx_spritecanvas.hxx" +#include "dx_winstuff.hxx" + + +using namespace ::com::sun::star; + +namespace dxcanvas +{ + SpriteCanvas::SpriteCanvas( const uno::Sequence< uno::Any >& aArguments, + const uno::Reference< uno::XComponentContext >& rxContext ) : + maArguments(aArguments), + mxComponentContext( rxContext ) + { + } + + void SpriteCanvas::initialize() + { + // #i64742# Only call initialize when not in probe mode + if( maArguments.getLength() == 0 ) + return; + + SAL_INFO("canvas.directx", "SpriteCanvas::initialize called" ); + + /* aArguments: + 0: ptr to creating instance (Window or VirtualDevice) + 1: SystemEnvData as a streamed Any (or empty for VirtualDevice) + 2: current bounds of creating instance + 3: bool, denoting always on top state for Window (always false for VirtualDevice) + 4: XWindow for creating Window (or empty for VirtualDevice) + 5: SystemGraphicsData as a streamed Any + */ + ENSURE_ARG_OR_THROW( maArguments.getLength() >= 4 && + maArguments[3].getValueTypeClass() == uno::TypeClass_INTERFACE, + "VCLSpriteCanvas::initialize: wrong number of arguments, or wrong types" ); + + uno::Reference< awt::XWindow > xParentWindow; + maArguments[3] >>= xParentWindow; + auto pParentWindow = VCLUnoHelper::GetWindow(xParentWindow); + if( !pParentWindow ) + throw lang::NoSupportException( "Parent window not VCL window, or canvas out-of-process!" ); + + awt::Rectangle aRect; + maArguments[1] >>= aRect; + + bool bIsFullscreen( false ); + maArguments[2] >>= bIsFullscreen; + + // setup helper + maDeviceHelper.init( *pParentWindow, + *this, + aRect, + bIsFullscreen ); + maCanvasHelper.init( *this, + maRedrawManager, + maDeviceHelper.getRenderModule(), + maDeviceHelper.getSurfaceProxy(), + maDeviceHelper.getBackBuffer(), + ::basegfx::B2ISize() ); + maArguments.realloc(0); + } + + void SpriteCanvas::disposeThis() + { + ::osl::MutexGuard aGuard( m_aMutex ); + + mxComponentContext.clear(); + + // forward to parent + SpriteCanvasBaseT::disposeThis(); + } + + sal_Bool SAL_CALL SpriteCanvas::showBuffer( sal_Bool bUpdateAll ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + + // avoid repaints on hidden window (hidden: not mapped to + // screen). Return failure, since the screen really has _not_ + // been updated (caller should try again later) + return mbIsVisible && SpriteCanvasBaseT::showBuffer( bUpdateAll ); + } + + sal_Bool SAL_CALL SpriteCanvas::switchBuffer( sal_Bool bUpdateAll ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + + // avoid repaints on hidden window (hidden: not mapped to + // screen). Return failure, since the screen really has _not_ + // been updated (caller should try again later) + return mbIsVisible && SpriteCanvasBaseT::switchBuffer( bUpdateAll ); + } + + sal_Bool SAL_CALL SpriteCanvas::updateScreen( sal_Bool bUpdateAll ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + + // avoid repaints on hidden window (hidden: not mapped to + // screen). Return failure, since the screen really has _not_ + // been updated (caller should try again later) + return mbIsVisible && maCanvasHelper.updateScreen( + ::basegfx::unotools::b2IRectangleFromAwtRectangle(maBounds), + bUpdateAll, + mbSurfaceDirty ); + } + + OUString SAL_CALL SpriteCanvas::getServiceName( ) + { + return "com.sun.star.rendering.SpriteCanvas.DX9"; + } + + // XServiceInfo + css::uno::Sequence<OUString> SpriteCanvas::getSupportedServiceNames( ) + { + return { "com.sun.star.rendering.SpriteCanvas.DX9" }; + } + OUString SpriteCanvas::getImplementationName( ) + { + return "com.sun.star.comp.rendering.SpriteCanvas.DX9"; + } + sal_Bool SpriteCanvas::supportsService( const OUString& sServiceName ) + { + return cppu::supportsService(this, sServiceName); + } + + const IDXRenderModuleSharedPtr& SpriteCanvas::getRenderModule() const + { + ::osl::MutexGuard aGuard( m_aMutex ); + + return maDeviceHelper.getRenderModule(); + } + + const DXSurfaceBitmapSharedPtr& SpriteCanvas::getBackBuffer() const + { + ::osl::MutexGuard aGuard( m_aMutex ); + + return maDeviceHelper.getBackBuffer(); + } + + IBitmapSharedPtr SpriteCanvas::getBitmap() const + { + return maDeviceHelper.getBackBuffer(); + } + + extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* + canvas_directx9_SpriteCanvas_get_implementation( + css::uno::XComponentContext* context, css::uno::Sequence<css::uno::Any> const& args) + { + rtl::Reference<SpriteCanvas> xCanvas(new SpriteCanvas(args, context)); + xCanvas->initialize(); + xCanvas->acquire(); + return static_cast<cppu::OWeakObject*>(xCanvas.get()); + } + +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_spritecanvas.hxx b/canvas/source/directx/dx_spritecanvas.hxx new file mode 100644 index 000000000..081337f72 --- /dev/null +++ b/canvas/source/directx/dx_spritecanvas.hxx @@ -0,0 +1,155 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include <rtl/ref.hxx> + +#include <com/sun/star/uno/XComponentContext.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/lang/XServiceName.hpp> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/awt/XWindowListener.hpp> +#include <com/sun/star/awt/XTopWindow.hpp> +#include <com/sun/star/util/XUpdatable.hpp> +#include <com/sun/star/rendering/XSpriteCanvas.hpp> +#include <com/sun/star/rendering/XIntegerBitmap.hpp> +#include <com/sun/star/rendering/XGraphicDevice.hpp> +#include <com/sun/star/rendering/XBufferController.hpp> + +#include <cppuhelper/compbase.hxx> +#include <comphelper/uno3.hxx> + +#include <base/spritecanvasbase.hxx> +#include <base/spritesurface.hxx> +#include <base/disambiguationhelper.hxx> +#include <base/bufferedgraphicdevicebase.hxx> + +#include "dx_bitmapprovider.hxx" +#include "dx_spritecanvashelper.hxx" +#include "dx_surfacebitmap.hxx" +#include "dx_impltools.hxx" +#include "dx_spritedevicehelper.hxx" + + +namespace dxcanvas +{ + typedef ::cppu::WeakComponentImplHelper< css::rendering::XSpriteCanvas, + css::rendering::XIntegerBitmap, + css::rendering::XGraphicDevice, + css::lang::XMultiServiceFactory, + css::rendering::XBufferController, + css::awt::XWindowListener, + css::util::XUpdatable, + css::beans::XPropertySet, + css::lang::XServiceName, + css::lang::XServiceInfo> WindowGraphicDeviceBase_Base; + typedef ::canvas::BufferedGraphicDeviceBase< ::canvas::DisambiguationHelper< WindowGraphicDeviceBase_Base >, + SpriteDeviceHelper, + ::osl::MutexGuard, + ::cppu::OWeakObject > SpriteCanvasBase_Base; + /** Mixin SpriteSurface + + Have to mixin the SpriteSurface before deriving from + ::canvas::SpriteCanvasBase, as this template should already + implement some of those interface methods. + + The reason why this appears kinda convoluted is the fact that + we cannot specify non-IDL types as WeakComponentImplHelper + template args, and furthermore, don't want to derive + ::canvas::SpriteCanvasBase directly from + ::canvas::SpriteSurface (because derivees of + ::canvas::SpriteCanvasBase have to explicitly forward the + XInterface methods (e.g. via DECLARE_UNO3_AGG_DEFAULTS) + anyway). Basically, ::canvas::CanvasCustomSpriteBase should + remain a base class that provides implementation, not to + enforce any specific interface on its derivees. + */ + class SpriteCanvasBaseSpriteSurface_Base : public SpriteCanvasBase_Base, + public ::canvas::SpriteSurface + { + }; + + typedef ::canvas::SpriteCanvasBase< SpriteCanvasBaseSpriteSurface_Base, + SpriteCanvasHelper, + ::osl::MutexGuard, + ::cppu::OWeakObject > SpriteCanvasBaseT; + + /** Product of this component's factory. + + The SpriteCanvas object combines the actual Window canvas with + the XGraphicDevice interface. This is because there's a + one-to-one relation between them, anyway, since each window + can have exactly one canvas and one associated + XGraphicDevice. And to avoid messing around with circular + references, this is implemented as one single object. + */ + class SpriteCanvas : public SpriteCanvasBaseT, public BitmapProvider + { + public: + SpriteCanvas( const css::uno::Sequence< + css::uno::Any >& aArguments, + const css::uno::Reference< + css::uno::XComponentContext >& rxContext ); + + void initialize(); + + /// Dispose all internal references + virtual void disposeThis() override; + + // Forwarding the XComponent implementation to the + // cppu::ImplHelper templated base + // Classname Base doing refcounting Base implementing the XComponent interface + // | | | + // V V V + DECLARE_UNO3_XCOMPONENT_AGG_DEFAULTS( SpriteCanvas, WindowGraphicDeviceBase_Base, ::cppu::WeakComponentImplHelperBase ) + + // XBufferController (partial) + virtual sal_Bool SAL_CALL showBuffer( sal_Bool bUpdateAll ) override; + virtual sal_Bool SAL_CALL switchBuffer( sal_Bool bUpdateAll ) override; + + // XSpriteCanvas (partial) + virtual sal_Bool SAL_CALL updateScreen( sal_Bool bUpdateAll ) override; + + // XServiceName + virtual OUString SAL_CALL getServiceName( ) override; + + // XServiceInfo + virtual css::uno::Sequence<OUString> SAL_CALL getSupportedServiceNames( ) override; + virtual OUString SAL_CALL getImplementationName( ) override; + virtual sal_Bool SAL_CALL supportsService( const OUString& ) override; + + /// Retrieve rendermodule object for this Canvas + const IDXRenderModuleSharedPtr& getRenderModule() const; + + /// Get backbuffer for this canvas + const DXSurfaceBitmapSharedPtr& getBackBuffer() const; + + // BitmapProvider + virtual IBitmapSharedPtr getBitmap() const override; + + private: + css::uno::Sequence< css::uno::Any > maArguments; + css::uno::Reference< css::uno::XComponentContext > mxComponentContext; + }; + + typedef ::rtl::Reference< SpriteCanvas > SpriteCanvasRef; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_spritecanvashelper.cxx b/canvas/source/directx/dx_spritecanvashelper.cxx new file mode 100644 index 000000000..416c66ba5 --- /dev/null +++ b/canvas/source/directx/dx_spritecanvashelper.cxx @@ -0,0 +1,352 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <boost/cast.hpp> + +#include <basegfx/range/b2drectangle.hxx> +#include <basegfx/utils/canvastools.hxx> +#include <comphelper/scopeguard.hxx> +#include <tools/diagnose_ex.h> + +#include <canvas/canvastools.hxx> + +#include "dx_canvascustomsprite.hxx" +#include "dx_spritecanvashelper.hxx" + +using namespace ::com::sun::star; + +namespace dxcanvas +{ + namespace + { + void repaintBackground( const ::basegfx::B2DRange& rUpdateArea, + const ::basegfx::B2IRange& rOutputArea, + const DXSurfaceBitmapSharedPtr& rBackBuffer ) + { + // TODO(E1): Use numeric_cast to catch overflow here + ::basegfx::B2IRange aActualArea( 0, 0, + static_cast<sal_Int32>(rOutputArea.getWidth()), + static_cast<sal_Int32>(rOutputArea.getHeight()) ); + aActualArea.intersect( fround( rUpdateArea ) ); + + // repaint the given area of the screen with background content + rBackBuffer->draw(aActualArea); + } + + void spriteRedraw( const ::canvas::Sprite::Reference& rSprite ) + { + // downcast to derived dxcanvas::Sprite interface, which + // provides the actual redraw methods. + ::boost::polymorphic_downcast< Sprite* >( + rSprite.get() )->redraw(); + } + } + + SpriteCanvasHelper::SpriteCanvasHelper() : + mpSpriteSurface( nullptr ), + mpRedrawManager( nullptr ), + mpRenderModule(), + mpSurfaceProxy(), + mpBackBuffer(), + maUpdateRect(), + maScrapRect(), + mbShowSpriteBounds( false ) + { +#if OSL_DEBUG_LEVEL > 0 + // inverse default for verbose debug mode + mbShowSpriteBounds = true; +#endif + } + + void SpriteCanvasHelper::init( SpriteCanvas& rParent, + ::canvas::SpriteRedrawManager& rManager, + const IDXRenderModuleSharedPtr& rRenderModule, + const std::shared_ptr<canvas::ISurfaceProxyManager>& rSurfaceProxy, + const DXSurfaceBitmapSharedPtr& rBackBuffer, + const ::basegfx::B2ISize& rOutputOffset ) + { + // init base + setDevice( rParent ); + setTarget( rBackBuffer, rOutputOffset ); + + mpSpriteSurface = &rParent; + mpRedrawManager = &rManager; + mpRenderModule = rRenderModule; + mpSurfaceProxy = rSurfaceProxy; + mpBackBuffer = rBackBuffer; + } + + void SpriteCanvasHelper::disposing() + { + if(mpRenderModule) + mpRenderModule->disposing(); + + mpBackBuffer.reset(); + mpRenderModule.reset(); + mpRedrawManager = nullptr; + mpSpriteSurface = nullptr; + + // forward to base + CanvasHelper::disposing(); + } + + uno::Reference< rendering::XAnimatedSprite > SpriteCanvasHelper::createSpriteFromAnimation( + const uno::Reference< rendering::XAnimation >& /*animation*/ ) + { + return uno::Reference< rendering::XAnimatedSprite >(); + } + + uno::Reference< rendering::XAnimatedSprite > SpriteCanvasHelper::createSpriteFromBitmaps( + const uno::Sequence< uno::Reference< rendering::XBitmap > >& /*animationBitmaps*/, + sal_Int8 /*interpolationMode*/ ) + { + return uno::Reference< rendering::XAnimatedSprite >(); + } + + uno::Reference< rendering::XCustomSprite > SpriteCanvasHelper::createCustomSprite( const geometry::RealSize2D& spriteSize ) + { + if( !mpRedrawManager ) + return uno::Reference< rendering::XCustomSprite >(); // we're disposed + + return uno::Reference< rendering::XCustomSprite >( + new CanvasCustomSprite( spriteSize, + mpSpriteSurface, + mpRenderModule, + mpSurfaceProxy, + mbShowSpriteBounds ) ); + } + + uno::Reference< rendering::XSprite > SpriteCanvasHelper::createClonedSprite( const uno::Reference< rendering::XSprite >& /*original*/ ) + { + return uno::Reference< rendering::XSprite >(); + } + + bool SpriteCanvasHelper::updateScreen( const ::basegfx::B2IRectangle& rCurrArea, + bool bUpdateAll, + bool& io_bSurfaceDirty ) + { + if( !mpRedrawManager || + !mpRenderModule || + !mpBackBuffer ) + { + return false; // disposed, or otherwise dysfunctional + } + + // store current output area (need to tunnel that to the + // background, scroll, opaque and general sprite repaint + // routines) + maScrapRect = rCurrArea; + + // clear area that needs to be blitted to screen beforehand + maUpdateRect.reset(); + + // TODO(P1): Might be worthwhile to track areas of background + // changes, too. + + // TODO(P2): Might be worthwhile to use page-flipping only if + // a certain percentage of screen area has changed - and + // compose directly to the front buffer otherwise. + if( !bUpdateAll && !io_bSurfaceDirty ) + { + // background has not changed, so we're free to optimize + // repaint to areas where a sprite has changed + + // process each independent area of overlapping sprites + // separately. + mpRedrawManager->forEachSpriteArea( *this ); + + // flip primary surface to screen + // ============================== + + // perform buffer flipping + mpRenderModule->flip( maUpdateRect, + rCurrArea ); + } + else + { + // limit update to parent window area (ignored for fullscreen) + // TODO(E1): Use numeric_cast to catch overflow here + const ::basegfx::B2IRectangle aUpdateArea( 0,0, + static_cast<sal_Int32>(rCurrArea.getWidth()), + static_cast<sal_Int32>(rCurrArea.getHeight()) ); + + // background has changed, or called requested full + // update, or we're performing double buffering via page + // flipping, so we currently have no choice but repaint + // everything + + // repaint the whole screen with background content + mpBackBuffer->draw(aUpdateArea); + + // redraw sprites + mpRedrawManager->forEachSprite( &spriteRedraw ); + + // flip primary surface to screen + // ============================== + + // perform buffer flipping + mpRenderModule->flip( aUpdateArea, + rCurrArea ); + } + + // change record vector must be cleared, for the next turn of + // rendering and sprite changing + mpRedrawManager->clearChangeRecords(); + + io_bSurfaceDirty = false; + + return true; + } + + void SpriteCanvasHelper::backgroundPaint( const ::basegfx::B2DRange& rUpdateRect ) + { + ENSURE_OR_THROW( mpRenderModule && + mpBackBuffer, + "SpriteCanvasHelper::backgroundPaint(): NULL device pointer " ); + + repaintBackground( rUpdateRect, + maScrapRect, + mpBackBuffer ); + } + + void SpriteCanvasHelper::scrollUpdate( const ::basegfx::B2DRange& /*rMoveStart*/, + const ::basegfx::B2DRange& rMoveEnd, + const ::canvas::SpriteRedrawManager::UpdateArea& rUpdateArea ) + { + ENSURE_OR_THROW( mpRenderModule && + mpBackBuffer, + "SpriteCanvasHelper::scrollUpdate(): NULL device pointer " ); + + // round rectangles to integer pixel. Note: have to be + // extremely careful here, to avoid off-by-one errors for + // the destination area: otherwise, the next scroll update + // would copy pixel that are not supposed to be part of + // the sprite. + const ::basegfx::B2IRange& rDestRect( + ::canvas::tools::spritePixelAreaFromB2DRange( rMoveEnd ) ); + + // not much sense in really implementing scrollUpdate here, + // since outputting a sprite only partially would result in + // expensive clipping. Furthermore, we cannot currently render + // 3D directly to the front buffer, thus, would have to blit + // the full sprite area, anyway. But at least optimized in the + // sense that unnecessary background paints behind the sprites + // are avoided. + for( const auto& rComponent : rUpdateArea.maComponentList ) + { + const ::canvas::Sprite::Reference& rSprite( rComponent.second.getSprite() ); + + if( rSprite.is() ) + { + // downcast to derived dxcanvas::Sprite interface, which + // provides the actual redraw methods. + ::boost::polymorphic_downcast< Sprite* >( rSprite.get() )->redraw(); + } + } + + // repaint uncovered areas from backbuffer - take the + // _rounded_ rectangles from above, to have the update + // consistent with the scroll above. + std::vector< ::basegfx::B2DRange > aUncoveredAreas; + ::basegfx::computeSetDifference( aUncoveredAreas, + rUpdateArea.maTotalBounds, + ::basegfx::B2DRange( rDestRect ) ); + for( const auto& rUncoveredArea : aUncoveredAreas ) + repaintBackground( rUncoveredArea, maScrapRect, mpBackBuffer ); + + // TODO(E1): Use numeric_cast to catch overflow here + ::basegfx::B2IRange aActualArea( 0, 0, + static_cast<sal_Int32>(maScrapRect.getWidth()), + static_cast<sal_Int32>(maScrapRect.getHeight()) ); + aActualArea.intersect( fround( rUpdateArea.maTotalBounds ) ); + + // add given update area to the 'blit to foreground' rect + maUpdateRect.expand( aActualArea ); + } + + void SpriteCanvasHelper::opaqueUpdate( const ::basegfx::B2DRange& rTotalArea, + const std::vector< ::canvas::Sprite::Reference >& rSortedUpdateSprites ) + { + ENSURE_OR_THROW( mpRenderModule && + mpBackBuffer, + "SpriteCanvasHelper::opaqueUpdate(): NULL device pointer " ); + + // TODO(P2): optimize this by truly rendering to the front + // buffer. Currently, we've the 3D device only for the back + // buffer. + for( const auto& rSprite : rSortedUpdateSprites ) + { + if( rSprite.is() ) + { + // downcast to derived dxcanvas::Sprite interface, which + // provides the actual redraw methods. + ::boost::polymorphic_downcast< Sprite* >( rSprite.get() )->redraw(); + } + } + + // TODO(E1): Use numeric_cast to catch overflow here + ::basegfx::B2IRange aActualArea( 0, 0, + static_cast<sal_Int32>(maScrapRect.getWidth()), + static_cast<sal_Int32>(maScrapRect.getHeight()) ); + aActualArea.intersect( fround( rTotalArea ) ); + + // add given update area to the 'blit to foreground' rect + maUpdateRect.expand( aActualArea ); + } + + void SpriteCanvasHelper::genericUpdate( const ::basegfx::B2DRange& rTotalArea, + const std::vector< ::canvas::Sprite::Reference >& rSortedUpdateSprites ) + { + ENSURE_OR_THROW( mpRenderModule && + mpBackBuffer, + "SpriteCanvasHelper::genericUpdate(): NULL device pointer " ); + + // paint background + // ================ + + // TODO(E1): Use numeric_cast to catch overflow here + ::basegfx::B2IRange aActualArea( 0, 0, + static_cast<sal_Int32>(maScrapRect.getWidth()), + static_cast<sal_Int32>(maScrapRect.getHeight()) ); + aActualArea.intersect( fround( rTotalArea ) ); + + // repaint the given area of the screen with background content + mpBackBuffer->draw(aActualArea); + + // paint sprite + // ============ + + for( const auto& rSprite : rSortedUpdateSprites ) + { + if( rSprite.is() ) + { + // downcast to derived dxcanvas::Sprite interface, which + // provides the actual redraw methods. + ::boost::polymorphic_downcast< Sprite* >( rSprite.get() )->redraw(); + } + } + + // add given update area to the 'blit to foreground' rect + maUpdateRect.expand( aActualArea ); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_spritecanvashelper.hxx b/canvas/source/directx/dx_spritecanvashelper.hxx new file mode 100644 index 000000000..5c2d98ba8 --- /dev/null +++ b/canvas/source/directx/dx_spritecanvashelper.hxx @@ -0,0 +1,152 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include <com/sun/star/rendering/XSpriteCanvas.hpp> +#include <com/sun/star/rendering/XIntegerBitmap.hpp> + +#include <spriteredrawmanager.hxx> +#include <rendering/isurfaceproxy.hxx> +#include <rendering/isurfaceproxymanager.hxx> + +#include "dx_bitmapcanvashelper.hxx" +#include "dx_impltools.hxx" +#include "dx_rendermodule.hxx" +#include "dx_surfacebitmap.hxx" + +#include <basegfx/range/b2irectangle.hxx> + +namespace dxcanvas +{ + class SpriteCanvas; + + class SpriteCanvasHelper : public BitmapCanvasHelper + { + public: + SpriteCanvasHelper(); + + void init( SpriteCanvas& rParent, + ::canvas::SpriteRedrawManager& rManager, + const IDXRenderModuleSharedPtr& rRenderModule, + const std::shared_ptr<canvas::ISurfaceProxyManager>& rSurfaceProxy, + const DXSurfaceBitmapSharedPtr& rBackBuffer, + const ::basegfx::B2ISize& rOutputOffset ); + + /// Dispose all internal references + void disposing(); + + // XSpriteCanvas + css::uno::Reference< + css::rendering::XAnimatedSprite > createSpriteFromAnimation( + const css::uno::Reference< css::rendering::XAnimation >& animation ); + + css::uno::Reference< + css::rendering::XAnimatedSprite > createSpriteFromBitmaps( + const css::uno::Sequence< + css::uno::Reference< + css::rendering::XBitmap > >& animationBitmaps, + sal_Int8 interpolationMode ); + + css::uno::Reference< + css::rendering::XCustomSprite > createCustomSprite( + const css::geometry::RealSize2D& spriteSize ); + + css::uno::Reference< + css::rendering::XSprite > createClonedSprite( + const css::uno::Reference< css::rendering::XSprite >& original ); + + /** Actually perform the screen update + + @param rCurrArea + Current window area in absolute screen coordinates + + @param bUpdateAll + sal_True, if everything must be updated, not only changed + sprites + + @param io_bSurfaceDirty + In/out parameter, whether backbuffer surface is dirty (if + yes, we're performing a full update, anyway) + */ + bool updateScreen( const ::basegfx::B2IRectangle& rCurrArea, + bool bUpdateAll, + bool& io_bSurfaceDirty ); + + + // SpriteRedrawManager functor calls + + + /** Gets called for simple background repaints + */ + void backgroundPaint( const ::basegfx::B2DRange& rUpdateRect ); + + /** Gets called when area can be handled by scrolling. + + Called method must copy screen content from rMoveStart to + rMoveEnd, and restore the background in the uncovered + areas. + + @param rMoveStart + Source rect of the scroll + + @param rMoveEnd + Dest rect of the scroll + + @param rUpdateArea + All info necessary, should rMoveStart be partially or + fully outside the outdev + */ + void scrollUpdate( const ::basegfx::B2DRange& rMoveStart, + const ::basegfx::B2DRange& rMoveEnd, + const ::canvas::SpriteRedrawManager::UpdateArea& rUpdateArea ); + + void opaqueUpdate( const ::basegfx::B2DRange& rTotalArea, + const std::vector< ::canvas::Sprite::Reference >& rSortedUpdateSprites ); + + void genericUpdate( const ::basegfx::B2DRange& rTotalArea, + const std::vector< ::canvas::Sprite::Reference >& rSortedUpdateSprites ); + + private: + /// For generating sprites + SpriteCanvas* mpSpriteSurface; + + /// Set from the SpriteCanvas: instance coordinating sprite redraw + ::canvas::SpriteRedrawManager* mpRedrawManager; + + /// DX device, handling all low-level rendering + IDXRenderModuleSharedPtr mpRenderModule; + + std::shared_ptr<canvas::ISurfaceProxyManager> mpSurfaceProxy; + + /// Backbuffer, contains the static canvas render output + DXSurfaceBitmapSharedPtr mpBackBuffer; + + /// Completely temporary rect storage (used by sprite repaint) + mutable ::basegfx::B2IRange maUpdateRect; + + /// Completely temporary rect storage (used by sprite repaint) + mutable ::basegfx::B2IRange maScrapRect; + + /// When true, show small bound rects around each sprite + bool mbShowSpriteBounds; + }; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_spritedevicehelper.cxx b/canvas/source/directx/dx_spritedevicehelper.cxx new file mode 100644 index 000000000..ee339c8af --- /dev/null +++ b/canvas/source/directx/dx_spritedevicehelper.cxx @@ -0,0 +1,221 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <memory> + +#include <sal/log.hxx> + +#include <basegfx/utils/canvastools.hxx> +#include <canvas/canvastools.hxx> +#include <com/sun/star/lang/NoSupportException.hpp> +#include <toolkit/helper/vclunohelper.hxx> +#include <tools/diagnose_ex.h> +#include <vcl/sysdata.hxx> +#include <vcl/window.hxx> + +#include "dx_canvasbitmap.hxx" +#include "dx_linepolypolygon.hxx" +#include "dx_spritecanvas.hxx" +#include "dx_spritedevicehelper.hxx" +#include "dx_winstuff.hxx" + + +using namespace ::com::sun::star; + +namespace dxcanvas +{ + SpriteDeviceHelper::SpriteDeviceHelper() : + DeviceHelper(), + mpSpriteCanvas( nullptr ), + mpBackBuffer(), + mpSurfaceProxyManager(), + mpRenderModule() + { + } + + void SpriteDeviceHelper::init( vcl::Window& rWindow, + SpriteCanvas& rSpriteCanvas, + const awt::Rectangle& rRect, + bool /*bFullscreen*/ ) + { + // #i60490# ensure backbuffer has sensible minimal size + const sal_Int32 w( std::max(sal_Int32(1),sal_Int32(rRect.Width))); + const sal_Int32 h( std::max(sal_Int32(1),sal_Int32(rRect.Height))); + + rSpriteCanvas.setWindow( + uno::Reference<awt::XWindow2>( + VCLUnoHelper::GetInterface(&rWindow), + uno::UNO_QUERY_THROW) ); + + const SystemEnvData *pData = rWindow.GetSystemData(); + const HWND hWnd = reinterpret_cast<HWND>(pData->hWnd); + if( !IsWindow( hWnd ) ) + throw lang::NoSupportException( "Passed window has invalid system window, or canvas out-of-process!" ); + + mpSpriteCanvas = &rSpriteCanvas; + + try + { + // setup directx rendermodule + mpRenderModule = createRenderModule( rWindow ); + } + catch (...) { + + throw lang::NoSupportException( "Could not create DirectX device!", + static_cast< ::cppu::OWeakObject* >(&rSpriteCanvas) ); + } + + // create the surfaceproxy manager + mpSurfaceProxyManager = ::canvas::createSurfaceProxyManager( mpRenderModule ); + + // #i60490# ensure backbuffer has sensible minimal size + mpBackBuffer = std::make_shared<DXSurfaceBitmap>( + ::basegfx::B2ISize(w,h), + mpSurfaceProxyManager, + mpRenderModule, + false); + + // Assumes: SystemChildWindow() has CS_OWNDC + DeviceHelper::init(GetDC(mpRenderModule->getHWND()),rWindow.GetOutDev(), rSpriteCanvas); + } + + void SpriteDeviceHelper::disposing() + { + // release all references + mpBackBuffer.reset(); + mpSurfaceProxyManager.reset(); + mpRenderModule.reset(); + mpSpriteCanvas = nullptr; + + DeviceHelper::disposing(); + } + + uno::Reference< rendering::XBitmap > SpriteDeviceHelper::createCompatibleBitmap( + const uno::Reference< rendering::XGraphicDevice >& /*rDevice*/, + const geometry::IntegerSize2D& size ) + { + if( !getDevice() ) + return uno::Reference< rendering::XBitmap >(); // we're disposed + + DXSurfaceBitmapSharedPtr pBitmap = std::make_shared<DXSurfaceBitmap>( + ::basegfx::unotools::b2ISizeFromIntegerSize2D(size), + mpSurfaceProxyManager, + mpRenderModule, + false); + + // create a 24bit RGB system memory surface + return uno::Reference< rendering::XBitmap >(new CanvasBitmap(pBitmap,getDevice())); + } + + uno::Reference< rendering::XVolatileBitmap > SpriteDeviceHelper::createVolatileBitmap( + const uno::Reference< rendering::XGraphicDevice >& /*rDevice*/, + const geometry::IntegerSize2D& /*size*/ ) + { + return uno::Reference< rendering::XVolatileBitmap >(); + } + + uno::Reference< rendering::XBitmap > SpriteDeviceHelper::createCompatibleAlphaBitmap( + const uno::Reference< rendering::XGraphicDevice >& /*rDevice*/, + const geometry::IntegerSize2D& size ) + { + if( !getDevice() ) + return uno::Reference< rendering::XBitmap >(); // we're disposed + + DXSurfaceBitmapSharedPtr pBitmap = std::make_shared<DXSurfaceBitmap>( + ::basegfx::unotools::b2ISizeFromIntegerSize2D(size), + mpSurfaceProxyManager, + mpRenderModule, + true); + + // create a 32bit ARGB system memory surface + return uno::Reference< rendering::XBitmap >(new CanvasBitmap(pBitmap,getDevice())); + } + + uno::Reference< rendering::XVolatileBitmap > SpriteDeviceHelper::createVolatileAlphaBitmap( + const uno::Reference< rendering::XGraphicDevice >& /*rDevice*/, + const geometry::IntegerSize2D& /*size*/ ) + { + return uno::Reference< rendering::XVolatileBitmap >(); + } + + void SpriteDeviceHelper::destroyBuffers() + { + // TODO(F3): implement XBufferStrategy interface. For now, we + // _always_ will have exactly one backbuffer + } + + bool SpriteDeviceHelper::showBuffer( bool, bool ) + { + SAL_WARN("canvas.directx", "Not supposed to be called, handled by SpriteCanvas"); + return false; + } + + bool SpriteDeviceHelper::switchBuffer( bool, bool ) + { + SAL_WARN("canvas.directx", "Not supposed to be called, handled by SpriteCanvas"); + return false; + } + + uno::Any SpriteDeviceHelper::isAccelerated() const + { + return css::uno::Any(true); + } + + void SpriteDeviceHelper::notifySizeUpdate( const awt::Rectangle& rBounds ) + { + // #i60490# ensure backbuffer has sensible minimal size + const sal_Int32 x(rBounds.X); + const sal_Int32 y(rBounds.Y); + const sal_Int32 w(std::max(sal_Int32(1),sal_Int32(rBounds.Width))); + const sal_Int32 h(std::max(sal_Int32(1),sal_Int32(rBounds.Height))); + + if( mpRenderModule ) + mpRenderModule->resize(::basegfx::B2IRange(x,y,x+w,y+h)); + + resizeBackBuffer(::basegfx::B2ISize(w,h)); + } + + void SpriteDeviceHelper::resizeBackBuffer( const ::basegfx::B2ISize& rNewSize ) + { + // disposed? + if(!mpBackBuffer) + return; + + mpBackBuffer->resize(rNewSize); + mpBackBuffer->clear(); + } + + HWND SpriteDeviceHelper::getHwnd() const + { + if( mpRenderModule ) + return mpRenderModule->getHWND(); + else + return nullptr; + } + + void SpriteDeviceHelper::dumpScreenContent() const + { + if( mpRenderModule ) + mpRenderModule->screenShot(); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_spritedevicehelper.hxx b/canvas/source/directx/dx_spritedevicehelper.hxx new file mode 100644 index 000000000..ad7417770 --- /dev/null +++ b/canvas/source/directx/dx_spritedevicehelper.hxx @@ -0,0 +1,98 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include <com/sun/star/awt/Rectangle.hpp> +#include <com/sun/star/rendering/XGraphicDevice.hpp> +#include <com/sun/star/rendering/XBufferController.hpp> + +#include "dx_rendermodule.hxx" +#include "dx_surfacebitmap.hxx" +#include "dx_devicehelper.hxx" + +#include <rendering/isurfaceproxymanager.hxx> + + +namespace dxcanvas +{ + class SpriteCanvas; + + class SpriteDeviceHelper : public DeviceHelper + { + public: + SpriteDeviceHelper(); + + void init( vcl::Window& rWindow, + SpriteCanvas& rSpriteCanvas, + const css::awt::Rectangle& rRect, + bool bFullscreen ); + + /// Dispose all internal references + void disposing(); + + // partial override XWindowGraphicDevice + css::uno::Reference< css::rendering::XBitmap > createCompatibleBitmap( + const css::uno::Reference< css::rendering::XGraphicDevice >& rDevice, + const css::geometry::IntegerSize2D& size ); + css::uno::Reference< css::rendering::XVolatileBitmap > createVolatileBitmap( + const css::uno::Reference< css::rendering::XGraphicDevice >& rDevice, + const css::geometry::IntegerSize2D& size ); + css::uno::Reference< css::rendering::XBitmap > createCompatibleAlphaBitmap( + const css::uno::Reference< css::rendering::XGraphicDevice >& rDevice, + const css::geometry::IntegerSize2D& size ); + css::uno::Reference< css::rendering::XVolatileBitmap > createVolatileAlphaBitmap( + const css::uno::Reference< css::rendering::XGraphicDevice >& rDevice, + const css::geometry::IntegerSize2D& size ); + + void destroyBuffers( ); + bool showBuffer( bool bIsVisible, bool bUpdateAll ); + bool switchBuffer( bool bIsVisible, bool bUpdateAll ); + + const IDXRenderModuleSharedPtr& getRenderModule() const { return mpRenderModule; } + const DXSurfaceBitmapSharedPtr& getBackBuffer() const { return mpBackBuffer; } + const std::shared_ptr<canvas::ISurfaceProxyManager> &getSurfaceProxy() const { return mpSurfaceProxyManager; } + + css::uno::Any isAccelerated() const; + + void notifySizeUpdate( const css::awt::Rectangle& rBounds ); + + /** called when DumpScreenContent property is enabled on + XGraphicDevice, and writes out bitmaps of current screen. + */ + void dumpScreenContent() const; + + private: + void resizeBackBuffer( const ::basegfx::B2ISize& rNewSize ); + HWND getHwnd() const; + + /// Pointer to sprite canvas (owner of this helper), needed to create bitmaps + SpriteCanvas* mpSpriteCanvas; + + DXSurfaceBitmapSharedPtr mpBackBuffer; + + /// Instance passing out HW textures + std::shared_ptr<canvas::ISurfaceProxyManager> mpSurfaceProxyManager; + + /// Our encapsulation interface to DirectX + IDXRenderModuleSharedPtr mpRenderModule; + }; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_spritehelper.cxx b/canvas/source/directx/dx_spritehelper.cxx new file mode 100644 index 000000000..135c59e3e --- /dev/null +++ b/canvas/source/directx/dx_spritehelper.cxx @@ -0,0 +1,199 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> +#include <sal/log.hxx> + +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/numeric/ftools.hxx> +#include <basegfx/point/b2dpoint.hxx> +#include <basegfx/polygon/b2dpolygoncutandtouch.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/polygon/b2dpolygontriangulator.hxx> +#include <basegfx/polygon/b2dpolypolygontools.hxx> +#include <basegfx/utils/canvastools.hxx> +#include <rtl/math.hxx> +#include <tools/diagnose_ex.h> + +#include <canvas/canvastools.hxx> + +#include "dx_canvascustomsprite.hxx" +#include "dx_impltools.hxx" +#include "dx_spritehelper.hxx" + +using namespace ::com::sun::star; + +namespace dxcanvas +{ + SpriteHelper::SpriteHelper() : + mpSpriteCanvas(), + mpBitmap(), + mbTextureDirty( true ), + mbShowSpriteBounds( false ) + { + } + + void SpriteHelper::init( const geometry::RealSize2D& rSpriteSize, + const SpriteCanvasRef& rSpriteCanvas, + const IDXRenderModuleSharedPtr& rRenderModule, + const DXSurfaceBitmapSharedPtr& rBitmap, + bool bShowSpriteBounds ) + { + ENSURE_OR_THROW( rSpriteCanvas && + rRenderModule && + rBitmap, + "SpriteHelper::init(): Invalid device, sprite canvas or surface" ); + + mpSpriteCanvas = rSpriteCanvas; + mpBitmap = rBitmap; + mbTextureDirty = true; + mbShowSpriteBounds = bShowSpriteBounds; + + // also init base class + CanvasCustomSpriteHelper::init( rSpriteSize, + rSpriteCanvas ); + } + + void SpriteHelper::disposing() + { + mpBitmap.reset(); + mpSpriteCanvas.clear(); + + // forward to parent + CanvasCustomSpriteHelper::disposing(); + } + + ::basegfx::B2DPolyPolygon SpriteHelper::polyPolygonFromXPolyPolygon2D( uno::Reference< rendering::XPolyPolygon2D >& xPoly ) const + { + return tools::polyPolygonFromXPolyPolygon2D( xPoly ); + } + + bool SpriteHelper::needRedraw() const + { + if( !mpBitmap || + !mpSpriteCanvas ) + { + return false; // we're disposed, no redraw necessary + } + + if( !isActive() || + ::basegfx::fTools::equalZero( getAlpha() ) ) + { + return false; // sprite is invisible + } + + return true; + } + + void SpriteHelper::redraw( bool& io_bSurfaceDirty ) const + { + if( !mpBitmap || + !mpSpriteCanvas ) + { + return; // we're disposed + } + + const ::basegfx::B2DPoint& rPos( getPosPixel() ); + const double fAlpha( getAlpha() ); + + if( isActive() && + !::basegfx::fTools::equalZero( fAlpha ) ) + { + + // TODO(Q2): For the time being, Device does not take a target + // surface, but always unconditionally renders to the + // background buffer. + + // log output pos in device pixel + SAL_INFO("canvas.directx", "SpriteHelper::redraw(): output pos is (" << + rPos.getX() << "," << rPos.getY() << ")" ); + + const ::basegfx::B2DVector& rSize( getSizePixel() ); + const ::basegfx::B2DHomMatrix& rTransform( getTransformation() ); + const uno::Reference< rendering::XPolyPolygon2D >& xClip( getClip() ); + + mbTextureDirty = false; + io_bSurfaceDirty = false; // state taken, and processed. + + ::basegfx::B2DPolyPolygon aClipPath; // empty for no clip + bool bIsClipRectangular( false ); // false, if no + // clip, or clip + // is complex + + // setup and apply clip (if any) + // ================================= + + if( xClip.is() ) + { + aClipPath = tools::polyPolygonFromXPolyPolygon2D( xClip ); + + const sal_Int32 nNumClipPolygons( aClipPath.count() ); + if( nNumClipPolygons ) + { + // TODO(P2): hold rectangle attribute directly + // at the XPolyPolygon2D + + // check whether the clip is rectangular + if( nNumClipPolygons == 1 ) + if( ::basegfx::utils::isRectangle( aClipPath.getB2DPolygon( 0 ) ) ) + bIsClipRectangular = true; + } + } + + const ::basegfx::B2DRectangle aSourceRect( 0.0, + 0.0, + rSize.getX(), + rSize.getY() ); + + // draw simple rectangular area if no clip is set. + if( !aClipPath.count() ) + { + mpBitmap->draw(fAlpha,rPos,rTransform); + } + else if( bIsClipRectangular ) + { + // apply a simple rect clip + // ======================== + + ::basegfx::B2DRectangle aClipBounds( + ::basegfx::utils::getRange( aClipPath ) ); + aClipBounds.intersect( aSourceRect ); + + mpBitmap->draw(fAlpha,rPos,aClipBounds,rTransform); + } + else + { + // apply clip the hard way + // ======================= + + mpBitmap->draw(fAlpha,rPos,aClipPath,rTransform); + } + + if( mbShowSpriteBounds ) + { + if( aClipPath.count() ) + { + // TODO(F2): Re-enable debug output + } + } + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_spritehelper.hxx b/canvas/source/directx/dx_spritehelper.hxx new file mode 100644 index 000000000..86e6dbaf6 --- /dev/null +++ b/canvas/source/directx/dx_spritehelper.hxx @@ -0,0 +1,102 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include <com/sun/star/rendering/XCustomSprite.hpp> + +#include <base/canvascustomspritehelper.hxx> + +#include <basegfx/point/b2dpoint.hxx> +#include <basegfx/vector/b2isize.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> + +#include "dx_spritecanvas.hxx" +#include "dx_surfacebitmap.hxx" + +namespace dxcanvas +{ + /* Definition of SpriteHelper class */ + + /** Helper class for canvas sprites. + + This class implements all sprite-related functionality, like + that available on the XSprite interface. + */ + class SpriteHelper : public ::canvas::CanvasCustomSpriteHelper + { + public: + /** Create sprite helper + */ + SpriteHelper(); + + /** Late-init the sprite helper + + @param rSpriteSize + Size of the sprite + + @param rSpriteCanvas + Sprite canvas this sprite is part of. Object stores + ref-counted reference to it, thus, don't forget to pass on + disposing()! + + @param rRenderModule + rendermodule to use + + @param rSpriteSurface + The surface of the sprite (not the DX texture, but the + persistent target of content rendering) + + @param bShowSpriteBounds + When true, little debug bound rects for sprites are shown + */ + void init( const css::geometry::RealSize2D& rSpriteSize, + const SpriteCanvasRef& rSpriteCanvas, + const IDXRenderModuleSharedPtr& rRenderModule, + const DXSurfaceBitmapSharedPtr& rBitmap, + bool bShowSpriteBounds ); + + void disposing(); + + /** Repaint sprite content via hardware to associated sprite + canvas + + @param io_bSurfaceDirty + Input/output parameter, whether the sprite content is + dirty or not. If texture was updated, set to false + + */ + void redraw( bool& io_bSurfaceDirty ) const; + + private: + virtual ::basegfx::B2DPolyPolygon polyPolygonFromXPolyPolygon2D( + css::uno::Reference< css::rendering::XPolyPolygon2D >& xPoly ) const override; + + /// Returns true, if the sprite _really_ needs redraw + bool needRedraw() const; + + SpriteCanvasRef mpSpriteCanvas; + + DXSurfaceBitmapSharedPtr mpBitmap; + mutable bool mbTextureDirty; // when true, texture needs update + bool mbShowSpriteBounds; // when true, debug bound rect for sprites is shown + }; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_surfacebitmap.cxx b/canvas/source/directx/dx_surfacebitmap.cxx new file mode 100644 index 000000000..b6e4e1248 --- /dev/null +++ b/canvas/source/directx/dx_surfacebitmap.cxx @@ -0,0 +1,654 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <memory> +#include <string.h> + +#include <com/sun/star/rendering/ColorComponentTag.hpp> + +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/range/b2irange.hxx> +#include <tools/diagnose_ex.h> +#include <rendering/icolorbuffer.hxx> + +#include "dx_graphicsprovider.hxx" +#include "dx_impltools.hxx" +#include "dx_surfacebitmap.hxx" +#include "dx_surfacegraphics.hxx" + +using namespace ::com::sun::star; + +namespace dxcanvas +{ + namespace + { + + // DXColorBuffer + + + struct DXColorBuffer : public canvas::IColorBuffer + { + public: + DXColorBuffer( const sal::systools::COMReference<surface_type>& rSurface, + const ::basegfx::B2IVector& rSize ) + : maSize(rSize) + , maLockedRect{} + , mpSurface(rSurface) + { + } + + // implementation of the 'IColorBuffer' interface + public: + + virtual sal_uInt8* lock() const override; + virtual void unlock() const override; + virtual sal_uInt32 getWidth() const override; + virtual sal_uInt32 getHeight() const override; + virtual sal_uInt32 getStride() const override; + virtual Format getFormat() const override; + + private: + + ::basegfx::B2IVector maSize; + mutable D3DLOCKED_RECT maLockedRect; + sal::systools::COMReference<surface_type> mpSurface; + }; + + sal_uInt8* DXColorBuffer::lock() const + { + if(SUCCEEDED(mpSurface->LockRect(&maLockedRect,nullptr,D3DLOCK_NOSYSLOCK|D3DLOCK_READONLY))) + return static_cast<sal_uInt8 *>(maLockedRect.pBits); + return nullptr; + } + + void DXColorBuffer::unlock() const + { + mpSurface->UnlockRect(); + } + + sal_uInt32 DXColorBuffer::getWidth() const + { + return maSize.getX(); + } + + sal_uInt32 DXColorBuffer::getHeight() const + { + return maSize.getY(); + } + + sal_uInt32 DXColorBuffer::getStride() const + { + return maLockedRect.Pitch; + } + + canvas::IColorBuffer::Format DXColorBuffer::getFormat() const + { + return canvas::IColorBuffer::Format::X8R8G8B8; + } + + + // GDIColorBuffer + + + struct GDIColorBuffer : public canvas::IColorBuffer + { + public: + + GDIColorBuffer( const BitmapSharedPtr& rSurface, + const ::basegfx::B2IVector& rSize ) + : maSize(rSize) + , aBmpData{} + , mpGDIPlusBitmap(rSurface) + { + } + + // implementation of the 'IColorBuffer' interface + public: + + virtual sal_uInt8* lock() const override; + virtual void unlock() const override; + virtual sal_uInt32 getWidth() const override; + virtual sal_uInt32 getHeight() const override; + virtual sal_uInt32 getStride() const override; + virtual Format getFormat() const override; + + private: + + ::basegfx::B2IVector maSize; + mutable Gdiplus::BitmapData aBmpData; + BitmapSharedPtr mpGDIPlusBitmap; + }; + + sal_uInt8* GDIColorBuffer::lock() const + { + aBmpData.Width = maSize.getX(); + aBmpData.Height = maSize.getY(); + aBmpData.Stride = 4*aBmpData.Width; + aBmpData.PixelFormat = PixelFormat32bppARGB; + aBmpData.Scan0 = nullptr; + const Gdiplus::Rect aRect( 0,0,aBmpData.Width,aBmpData.Height ); + if( Gdiplus::Ok != mpGDIPlusBitmap->LockBits( &aRect, + Gdiplus::ImageLockModeRead, + PixelFormat32bppARGB, + &aBmpData ) ) + { + return nullptr; + } + + return static_cast<sal_uInt8*>(aBmpData.Scan0); + } + + void GDIColorBuffer::unlock() const + { + mpGDIPlusBitmap->UnlockBits( &aBmpData ); + } + + sal_uInt32 GDIColorBuffer::getWidth() const + { + return maSize.getX(); + } + + sal_uInt32 GDIColorBuffer::getHeight() const + { + return maSize.getY(); + } + + sal_uInt32 GDIColorBuffer::getStride() const + { + return aBmpData.Stride; + } + + canvas::IColorBuffer::Format GDIColorBuffer::getFormat() const + { + return canvas::IColorBuffer::Format::A8R8G8B8; + } + } + + + // DXSurfaceBitmap::DXSurfaceBitmap + + + DXSurfaceBitmap::DXSurfaceBitmap( const ::basegfx::B2IVector& rSize, + const std::shared_ptr<canvas::ISurfaceProxyManager>& rMgr, + const IDXRenderModuleSharedPtr& rRenderModule, + bool bWithAlpha ) : + mpGdiPlusUser( GDIPlusUser::createInstance() ), + maSize(rSize), + mpRenderModule(rRenderModule), + mpSurfaceManager(rMgr), + mpSurfaceProxy(), + mpSurface(), + mpGDIPlusBitmap(), + mpGraphics(), + mpColorBuffer(), + mbIsSurfaceDirty(true), + mbAlpha(bWithAlpha) + { + init(); + } + + + // DXSurfaceBitmap::getSize + + + ::basegfx::B2IVector DXSurfaceBitmap::getSize() const + { + return maSize; + } + + + // DXSurfaceBitmap::init + + + void DXSurfaceBitmap::init() + { + // create container for pixel data + if(mbAlpha) + { + mpGDIPlusBitmap = std::make_shared<Gdiplus::Bitmap>( + maSize.getX(), + maSize.getY(), + PixelFormat32bppARGB + ); + mpGraphics = tools::createGraphicsFromBitmap(mpGDIPlusBitmap); + + // create the colorbuffer object, which is basically a simple + // wrapper around the directx surface. the colorbuffer is the + // interface which is used by the surfaceproxy to support any + // kind of underlying structure for the pixel data container. + mpColorBuffer = std::make_shared<GDIColorBuffer>(mpGDIPlusBitmap,maSize); + } + else + { + mpSurface = mpRenderModule->createSystemMemorySurface(maSize); + + // create the colorbuffer object, which is basically a simple + // wrapper around the directx surface. the colorbuffer is the + // interface which is used by the surfaceproxy to support any + // kind of underlying structure for the pixel data container. + mpColorBuffer = std::make_shared<DXColorBuffer>(mpSurface,maSize); + } + + // create a (possibly hardware accelerated) mirror surface. + mpSurfaceProxy = mpSurfaceManager->createSurfaceProxy(mpColorBuffer); + } + + + // DXSurfaceBitmap::resize + + + bool DXSurfaceBitmap::resize( const ::basegfx::B2IVector& rSize ) + { + if(maSize != rSize) + { + maSize = rSize; + init(); + } + + return true; + } + + + // DXSurfaceBitmap::clear + + + void DXSurfaceBitmap::clear() + { + GraphicsSharedPtr pGraphics(getGraphics()); + Gdiplus::Color transColor(255,0,0,0); + pGraphics->SetCompositingMode( Gdiplus::CompositingModeSourceCopy ); + pGraphics->Clear( transColor ); + } + + + // DXSurfaceBitmap::hasAlpha + + + bool DXSurfaceBitmap::hasAlpha() const + { + return mbAlpha; + } + + + // DXSurfaceBitmap::getGraphics + + + GraphicsSharedPtr DXSurfaceBitmap::getGraphics() + { + // since clients will most probably draw directly + // to the GDI+ bitmap, we need to mark it as dirty + // to ensure that the corresponding dxsurface will + // be updated. + mbIsSurfaceDirty = true; + + if(hasAlpha()) + return mpGraphics; + else + return createSurfaceGraphics(mpSurface); + } + + + // DXSurfaceBitmap::getBitmap + + + BitmapSharedPtr DXSurfaceBitmap::getBitmap() const + { + if(hasAlpha()) + return mpGDIPlusBitmap; + + BitmapSharedPtr pResult; + + D3DLOCKED_RECT aLockedRect; + if(SUCCEEDED(mpSurface->LockRect(&aLockedRect,nullptr,D3DLOCK_NOSYSLOCK|D3DLOCK_READONLY))) + { + // decide about the format we pass the gdi+, the directx surface is always + // 32bit, either with or without alpha component. + Gdiplus::PixelFormat nFormat = hasAlpha() ? PixelFormat32bppARGB : PixelFormat32bppRGB; + + // construct a gdi+ bitmap from the raw pixel data. + pResult = std::make_shared<Gdiplus::Bitmap>( maSize.getX(),maSize.getY(), + aLockedRect.Pitch, + nFormat, + static_cast<BYTE *>(aLockedRect.pBits) ); + + mpSurface->UnlockRect(); + } + + return pResult; + } + + + // DXSurfaceBitmap::draw + + + bool DXSurfaceBitmap::draw( double fAlpha, + const ::basegfx::B2DPoint& rPos, + const ::basegfx::B2DPolyPolygon& rClipPoly, + const ::basegfx::B2DHomMatrix& rTransform ) + { + if( mbIsSurfaceDirty ) + { + mpSurfaceProxy->setColorBufferDirty(); + mbIsSurfaceDirty = false; + } + + return mpSurfaceProxy->draw( fAlpha, rPos, rClipPoly, rTransform ); + } + + + // DXSurfaceBitmap::draw + + + bool DXSurfaceBitmap::draw( double fAlpha, + const ::basegfx::B2DPoint& rPos, + const ::basegfx::B2DRange& rArea, + const ::basegfx::B2DHomMatrix& rTransform ) + { + if( mbIsSurfaceDirty ) + { + mpSurfaceProxy->setColorBufferDirty(); + mbIsSurfaceDirty = false; + } + + return mpSurfaceProxy->draw( fAlpha, rPos, rArea, rTransform ); + } + + + // DXSurfaceBitmap::draw + + + bool DXSurfaceBitmap::draw( double fAlpha, + const ::basegfx::B2DPoint& rPos, + const ::basegfx::B2DHomMatrix& rTransform ) + { + if( mbIsSurfaceDirty ) + { + mpSurfaceProxy->setColorBufferDirty(); + mbIsSurfaceDirty = false; + } + + return mpSurfaceProxy->draw( fAlpha, rPos, rTransform ); + } + + + // DXSurfaceBitmap::draw + + + bool DXSurfaceBitmap::draw( const ::basegfx::B2IRange& rArea ) + { + if( mbIsSurfaceDirty ) + { + mpSurfaceProxy->setColorBufferDirty(); + mbIsSurfaceDirty = false; + } + + const double fAlpha(1.0); + const ::basegfx::B2DHomMatrix aTransform; + const ::basegfx::B2DRange aIEEEArea( rArea ); + return mpSurfaceProxy->draw(fAlpha, + ::basegfx::B2DPoint(), + aIEEEArea, + aTransform); + } + + + // DXSurfaceBitmap::getData + + + uno::Sequence< sal_Int8 > DXSurfaceBitmap::getData( rendering::IntegerBitmapLayout& rBitmapLayout, + const geometry::IntegerRectangle2D& rect ) + { + if(hasAlpha()) + { + uno::Sequence< sal_Int8 > aRes( (rect.X2-rect.X1)*(rect.Y2-rect.Y1)*4 ); // TODO(F1): Be format-agnostic here + + const Gdiplus::Rect aRect( tools::gdiPlusRectFromIntegerRectangle2D( rect ) ); + + Gdiplus::BitmapData aBmpData; + aBmpData.Width = rect.X2-rect.X1; + aBmpData.Height = rect.Y2-rect.Y1; + aBmpData.Stride = 4*aBmpData.Width; + aBmpData.PixelFormat = PixelFormat32bppARGB; + aBmpData.Scan0 = aRes.getArray(); + + // TODO(F1): Support more pixel formats natively + + // read data from bitmap + if( Gdiplus::Ok != mpGDIPlusBitmap->LockBits( &aRect, + Gdiplus::ImageLockModeRead | Gdiplus::ImageLockModeUserInputBuf, + PixelFormat32bppARGB, // TODO(F1): Adapt to + // Graphics native + // format/change + // getMemoryLayout + &aBmpData ) ) + { + // failed to lock, bail out + return uno::Sequence< sal_Int8 >(); + } + + mpGDIPlusBitmap->UnlockBits( &aBmpData ); + + return aRes; + } + else + { + sal_uInt32 nWidth = rect.X2-rect.X1; + sal_uInt32 nHeight = rect.Y2-rect.Y1; + + uno::Sequence< sal_Int8 > aRes(nWidth*nHeight*4); + + D3DLOCKED_RECT aLockedRect; + if(FAILED(mpSurface->LockRect(&aLockedRect,nullptr,D3DLOCK_NOSYSLOCK|D3DLOCK_READONLY))) + return uno::Sequence< sal_Int8 >(); + D3DSURFACE_DESC aDesc; + if(FAILED(mpSurface->GetDesc(&aDesc))) + return uno::Sequence< sal_Int8 >(); + + assert(aDesc.Format == D3DFMT_A8R8G8B8 || aDesc.Format == D3DFMT_X8R8G8B8); + + sal_uInt8 *pSrc = (static_cast<BYTE *>(aLockedRect.pBits)+(rect.Y1*aLockedRect.Pitch))+rect.X1; + sal_uInt8 *pDst = reinterpret_cast<sal_uInt8 *>(aRes.getArray()); + sal_uInt32 nSegmentSizeInBytes = nWidth*4; + for(sal_uInt32 y=0; y<nHeight; ++y) + { + memcpy(pDst,pSrc,nSegmentSizeInBytes); + pDst += nSegmentSizeInBytes; + pSrc += aLockedRect.Pitch; + } + + if(rBitmapLayout.ColorSpace->getComponentTags().getArray()[0] == rendering::ColorComponentTag::RGB_RED && + rBitmapLayout.ColorSpace->getComponentTags().getArray()[2] == rendering::ColorComponentTag::RGB_BLUE) + { + pDst = reinterpret_cast<sal_uInt8 *>(aRes.getArray()); + for(sal_uInt32 y=0; y<nHeight; ++y) + { + sal_uInt8* pPixel = pDst; + for(sal_uInt32 n = 0; n<nWidth; n++) + { + sal_uInt8 nB = pPixel[0]; + pPixel[0] = pPixel[2]; + pPixel[2] = nB; + pPixel += 4; + } + pDst += nSegmentSizeInBytes; + } + } + + mpSurface->UnlockRect(); + return aRes; + } + } + + + // DXSurfaceBitmap::setData + + + void DXSurfaceBitmap::setData( const uno::Sequence< sal_Int8 >& data, + const rendering::IntegerBitmapLayout& /*bitmapLayout*/, + const geometry::IntegerRectangle2D& rect ) + { + if(hasAlpha()) + { + const Gdiplus::Rect aRect( tools::gdiPlusRectFromIntegerRectangle2D( rect ) ); + + Gdiplus::BitmapData aBmpData; + aBmpData.Width = rect.X2-rect.X1; + aBmpData.Height = rect.Y2-rect.Y1; + aBmpData.Stride = 4*aBmpData.Width; + aBmpData.PixelFormat = PixelFormat32bppARGB; + aBmpData.Scan0 = const_cast<sal_Int8 *>(data.getConstArray()); + + // TODO(F1): Support more pixel formats natively + + if( Gdiplus::Ok != mpGDIPlusBitmap->LockBits( &aRect, + Gdiplus::ImageLockModeWrite | Gdiplus::ImageLockModeUserInputBuf, + PixelFormat32bppARGB, // TODO: Adapt to + // Graphics native + // format/change + // getMemoryLayout + &aBmpData ) ) + { + throw uno::RuntimeException("GDIPlus method call was unsuccessful, problem with locking bitmap aRect object"); + } + + // commit data to bitmap + mpGDIPlusBitmap->UnlockBits( &aBmpData ); + } + else + { + sal_uInt32 nWidth = rect.X2-rect.X1; + sal_uInt32 nHeight = rect.Y2-rect.Y1; + + // lock the directx surface to receive the pointer to the surface memory. + D3DLOCKED_RECT aLockedRect; + if(FAILED(mpSurface->LockRect(&aLockedRect,nullptr,D3DLOCK_NOSYSLOCK|D3DLOCK_READONLY))) + throw uno::RuntimeException("failed to lock directx surface to surface memory"); + + sal_uInt8 const *pSrc = reinterpret_cast<sal_uInt8 const *>(data.getConstArray()); + sal_uInt8 *pDst = (static_cast<BYTE *>(aLockedRect.pBits)+(rect.Y1*aLockedRect.Pitch))+rect.X1; + sal_uInt32 nSegmentSizeInBytes = nWidth<<4; + for(sal_uInt32 y=0; y<nHeight; ++y) + { + memcpy(pDst,pSrc,nSegmentSizeInBytes); + pSrc += nSegmentSizeInBytes; + pDst += aLockedRect.Pitch; + } + + mpSurface->UnlockRect(); + } + + mbIsSurfaceDirty = true; + } + + + // DXSurfaceBitmap::setPixel + + + void DXSurfaceBitmap::setPixel( const uno::Sequence< sal_Int8 >& color, + const rendering::IntegerBitmapLayout& /*bitmapLayout*/, + const geometry::IntegerPoint2D& pos ) + { + if(hasAlpha()) + { + const geometry::IntegerSize2D aSize( maSize.getX(),maSize.getY() ); + + ENSURE_ARG_OR_THROW( pos.X >= 0 && pos.X < aSize.Width, + "CanvasHelper::setPixel: X coordinate out of bounds" ); + ENSURE_ARG_OR_THROW( pos.Y >= 0 && pos.Y < aSize.Height, + "CanvasHelper::setPixel: Y coordinate out of bounds" ); + ENSURE_ARG_OR_THROW( color.getLength() > 3, + "CanvasHelper::setPixel: not enough color components" ); + + if( Gdiplus::Ok != mpGDIPlusBitmap->SetPixel( pos.X, pos.Y, + Gdiplus::Color( tools::sequenceToArgb( color )))) + { + throw uno::RuntimeException("Problem with setting the color of bitmap object"); + } + } + else + { + ENSURE_ARG_OR_THROW( pos.X >= 0 && pos.X < maSize.getX(), + "CanvasHelper::setPixel: X coordinate out of bounds" ); + ENSURE_ARG_OR_THROW( pos.Y >= 0 && pos.Y < maSize.getY(), + "CanvasHelper::setPixel: Y coordinate out of bounds" ); + ENSURE_ARG_OR_THROW( color.getLength() > 3, + "CanvasHelper::setPixel: not enough color components" ); + + Gdiplus::Color aColor(tools::sequenceToArgb(color)); + + // lock the directx surface to receive the pointer to the surface memory. + D3DLOCKED_RECT aLockedRect; + if(FAILED(mpSurface->LockRect(&aLockedRect,nullptr,D3DLOCK_NOSYSLOCK|D3DLOCK_READONLY))) + throw uno::RuntimeException("cannot lock the directx surface to surface memory"); + + sal_uInt32 *pDst = reinterpret_cast<sal_uInt32 *>((static_cast<BYTE *>(aLockedRect.pBits)+(pos.Y*aLockedRect.Pitch))+pos.X); + *pDst = aColor.GetValue(); + mpSurface->UnlockRect(); + } + + mbIsSurfaceDirty = true; + } + + + // DXSurfaceBitmap::getPixel + + + uno::Sequence< sal_Int8 > DXSurfaceBitmap::getPixel( rendering::IntegerBitmapLayout& /*bitmapLayout*/, + const geometry::IntegerPoint2D& pos ) + { + if(hasAlpha()) + { + const geometry::IntegerSize2D aSize( maSize.getX(),maSize.getY() ); + + ENSURE_ARG_OR_THROW( pos.X >= 0 && pos.X < aSize.Width, + "CanvasHelper::getPixel: X coordinate out of bounds" ); + ENSURE_ARG_OR_THROW( pos.Y >= 0 && pos.Y < aSize.Height, + "CanvasHelper::getPixel: Y coordinate out of bounds" ); + + Gdiplus::Color aColor; + + if( Gdiplus::Ok != mpGDIPlusBitmap->GetPixel( pos.X, pos.Y, &aColor ) ) + return uno::Sequence< sal_Int8 >(); + + return tools::argbToIntSequence(aColor.GetValue()); + } + else + { + ENSURE_ARG_OR_THROW( pos.X >= 0 && pos.X < maSize.getX(), + "CanvasHelper::getPixel: X coordinate out of bounds" ); + ENSURE_ARG_OR_THROW( pos.Y >= 0 && pos.Y < maSize.getY(), + "CanvasHelper::getPixel: Y coordinate out of bounds" ); + + // lock the directx surface to receive the pointer to the surface memory. + D3DLOCKED_RECT aLockedRect; + if(FAILED(mpSurface->LockRect(&aLockedRect,nullptr,D3DLOCK_NOSYSLOCK|D3DLOCK_READONLY))) + throw uno::RuntimeException("failed to lock directX surface to surface memory"); + + sal_uInt32 *pDst = reinterpret_cast<sal_uInt32 *>((static_cast<BYTE *>(aLockedRect.pBits)+(pos.Y*aLockedRect.Pitch))+pos.X); + Gdiplus::Color aColor(*pDst); + mpSurface->UnlockRect(); + + return tools::argbToIntSequence(aColor.GetValue()); + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_surfacebitmap.hxx b/canvas/source/directx/dx_surfacebitmap.hxx new file mode 100644 index 000000000..ec71f4823 --- /dev/null +++ b/canvas/source/directx/dx_surfacebitmap.hxx @@ -0,0 +1,135 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include <rendering/isurfaceproxy.hxx> +#include <rendering/isurfaceproxymanager.hxx> +#include "dx_ibitmap.hxx" +#include "dx_canvasfont.hxx" +#include "dx_gdiplususer.hxx" +#include "dx_rendermodule.hxx" + +namespace dxcanvas +{ + class DXSurfaceBitmap : public IBitmap + { + public: + DXSurfaceBitmap( const ::basegfx::B2IVector& rSize, + const std::shared_ptr<canvas::ISurfaceProxyManager>& rMgr, + const IDXRenderModuleSharedPtr& rRenderModule, + bool bWithAlpha ); + + bool resize( const ::basegfx::B2IVector& rSize ); + void clear(); + + virtual GraphicsSharedPtr getGraphics() override; + + virtual BitmapSharedPtr getBitmap() const override; + virtual ::basegfx::B2IVector getSize() const override; + virtual bool hasAlpha() const override; + + sal::systools::COMReference<surface_type> getSurface() const { return mpSurface; } + + bool draw( double fAlpha, + const ::basegfx::B2DPoint& rPos, + const ::basegfx::B2DHomMatrix& rTransform ); + + bool draw( const ::basegfx::B2IRange& rArea ); + + bool draw( double fAlpha, + const ::basegfx::B2DPoint& rPos, + const ::basegfx::B2DRange& rArea, + const ::basegfx::B2DHomMatrix& rTransform ); + + bool draw( double fAlpha, + const ::basegfx::B2DPoint& rPos, + const ::basegfx::B2DPolyPolygon& rClipPoly, + const ::basegfx::B2DHomMatrix& rTransform ); + + virtual css::uno::Sequence< sal_Int8 > getData( + css::rendering::IntegerBitmapLayout& bitmapLayout, + const css::geometry::IntegerRectangle2D& rect ) override; + + virtual void setData( + const css::uno::Sequence< sal_Int8 >& data, + const css::rendering::IntegerBitmapLayout& bitmapLayout, + const css::geometry::IntegerRectangle2D& rect ) override; + + virtual void setPixel( + const css::uno::Sequence< sal_Int8 >& color, + const css::rendering::IntegerBitmapLayout& bitmapLayout, + const css::geometry::IntegerPoint2D& pos ) override; + + virtual css::uno::Sequence< sal_Int8 > getPixel( + css::rendering::IntegerBitmapLayout& bitmapLayout, + const css::geometry::IntegerPoint2D& pos ) override; + + private: + void init(); + + // Refcounted global GDI+ state container + GDIPlusUserSharedPtr mpGdiPlusUser; + + // size of this image in pixels [integral unit] + ::basegfx::B2IVector maSize; + + // pointer to the rendermodule, needed to create surfaces + // which are used as container for the actual pixel data. + // generally we could use any kind of storage, but GDI+ + // is not willing to render antialiased fonts unless we + // use this special kind of container, don't ask me why... + IDXRenderModuleSharedPtr mpRenderModule; + + // pointer to the surface manager, needed in case clients + // want to resize the bitmap. + std::shared_ptr<canvas::ISurfaceProxyManager> mpSurfaceManager; + + // access point to the surface proxy which handles + // the hardware-dependent rendering stuff. + std::shared_ptr< canvas::ISurfaceProxy > mpSurfaceProxy; + + // container for pixel data, we need to use a directx + // surface since GDI+ sucks... + sal::systools::COMReference<surface_type> mpSurface; + + // since GDI+ does not work correctly in case we + // run on a 16bit display [don't ask me why] we need + // to occasionally render to a native GDI+ bitmap. + BitmapSharedPtr mpGDIPlusBitmap; + // Graphics for the mpGDIPlusBitmap + GraphicsSharedPtr mpGraphics; + + // internal implementation of the iColorBuffer interface + std::shared_ptr<canvas::IColorBuffer> mpColorBuffer; + + // indicates whether the associated surface needs + // to refresh its contents or not. in other words, + // this flag is set iff both representations are + // out of sync. + mutable bool mbIsSurfaceDirty; + + // true if the bitmap contains an alpha channel + bool mbAlpha; + }; + + typedef std::shared_ptr< DXSurfaceBitmap > DXSurfaceBitmapSharedPtr; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_surfacegraphics.cxx b/canvas/source/directx/dx_surfacegraphics.cxx new file mode 100644 index 000000000..075f2fc15 --- /dev/null +++ b/canvas/source/directx/dx_surfacegraphics.cxx @@ -0,0 +1,77 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include "dx_impltools.hxx" +#include "dx_surfacegraphics.hxx" + +using namespace ::com::sun::star; + +namespace dxcanvas +{ + namespace + { + struct GraphicsDeleter + { + sal::systools::COMReference<surface_type> mpSurface; + HDC maHDC; + + GraphicsDeleter(const sal::systools::COMReference<surface_type>& rSurface, HDC hdc) : + mpSurface(rSurface), + maHDC(hdc) + {} + + void operator()( Gdiplus::Graphics* pGraphics ) + { + if(!pGraphics) + return; + + pGraphics->Flush(Gdiplus::FlushIntentionSync); + delete pGraphics; + + if(mpSurface.is()) + mpSurface->ReleaseDC( maHDC ); + } + }; + } + + GraphicsSharedPtr createSurfaceGraphics(const sal::systools::COMReference<surface_type>& rSurface ) + { + GraphicsSharedPtr pRet; + HDC aHDC; + if( SUCCEEDED(rSurface->GetDC( &aHDC )) ) + { + Gdiplus::Graphics* pGraphics = Gdiplus::Graphics::FromHDC( aHDC ); + if(pGraphics) + { + tools::setupGraphics( *pGraphics ); + pRet.reset(pGraphics, + GraphicsDeleter(rSurface, aHDC)); + return pRet; + } + else + rSurface->ReleaseDC( aHDC ); + } + + throw uno::RuntimeException("could not get the DC to rSurface"); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_surfacegraphics.hxx b/canvas/source/directx/dx_surfacegraphics.hxx new file mode 100644 index 000000000..4260383e2 --- /dev/null +++ b/canvas/source/directx/dx_surfacegraphics.hxx @@ -0,0 +1,36 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include "dx_graphicsprovider.hxx" + +namespace dxcanvas +{ +/** Container providing a Gdiplus::Graphics for a Surface + + This wrapper class transparently handles allocation and + release of surface resources the RAII way (the + GraphicsSharedPtr returned has a deleter that does all the + necessary DX cleanup work). +*/ +GraphicsSharedPtr createSurfaceGraphics(const sal::systools::COMReference<surface_type>& rSurface); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_textlayout.cxx b/canvas/source/directx/dx_textlayout.cxx new file mode 100644 index 000000000..452b0d48e --- /dev/null +++ b/canvas/source/directx/dx_textlayout.cxx @@ -0,0 +1,232 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> +#include <sal/log.hxx> + +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/numeric/ftools.hxx> +#include <cppuhelper/supportsservice.hxx> + +#include "dx_bitmap.hxx" +#include "dx_spritecanvas.hxx" +#include "dx_textlayout.hxx" +#include "dx_textlayout_drawhelper.hxx" + +using namespace ::com::sun::star; + +namespace dxcanvas +{ + TextLayout::TextLayout( const rendering::StringContext& aText, + sal_Int8 nDirection, + sal_Int64 /*nRandomSeed*/, + const CanvasFont::ImplRef& rFont ) : + TextLayout_Base( m_aMutex ), + maText( aText ), + maLogicalAdvancements(), + mpFont( rFont ), + mnTextDirection( nDirection ) + { + } + + TextLayout::~TextLayout() + { + } + + void SAL_CALL TextLayout::disposing() + { + mpFont.clear(); + } + + // XTextLayout + uno::Sequence< uno::Reference< rendering::XPolyPolygon2D > > SAL_CALL TextLayout::queryTextShapes( ) + { + // TODO + return uno::Sequence< uno::Reference< rendering::XPolyPolygon2D > >(); + } + + uno::Sequence< geometry::RealRectangle2D > SAL_CALL TextLayout::queryInkMeasures( ) + { + // TODO + return uno::Sequence< geometry::RealRectangle2D >(); + } + + uno::Sequence< geometry::RealRectangle2D > SAL_CALL TextLayout::queryMeasures( ) + { + // TODO + return uno::Sequence< geometry::RealRectangle2D >(); + } + + uno::Sequence< double > SAL_CALL TextLayout::queryLogicalAdvancements( ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + + return maLogicalAdvancements; + } + + void SAL_CALL TextLayout::applyLogicalAdvancements( const uno::Sequence< double >& aAdvancements ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + + if( aAdvancements.getLength() != maText.Length ) + { + SAL_WARN("canvas.directx", "TextLayout::applyLogicalAdvancements(): mismatching number of advancements" ); + throw lang::IllegalArgumentException(); + } + + maLogicalAdvancements = aAdvancements; + } + + geometry::RealRectangle2D SAL_CALL TextLayout::queryTextBounds( ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + + uno::Reference< rendering::XGraphicDevice > xGraphicDevice; + ::dxcanvas::TextLayoutDrawHelper aDrawHelper(xGraphicDevice); + + // render text + const geometry::RealRectangle2D aBounds( + aDrawHelper.queryTextBounds( + maText, + maLogicalAdvancements, + mpFont, + mpFont->getFontMatrix())); + + return aBounds; + } + + double SAL_CALL TextLayout::justify( double /*nSize*/ ) + { + // TODO + return 0.0; + } + + double SAL_CALL TextLayout::combinedJustify( const uno::Sequence< uno::Reference< rendering::XTextLayout > >& /*aNextLayouts*/, + double /*nSize*/ ) + { + // TODO + return 0.0; + } + + rendering::TextHit SAL_CALL TextLayout::getTextHit( const geometry::RealPoint2D& /*aHitPoint*/ ) + { + // TODO + return rendering::TextHit(); + } + + rendering::Caret SAL_CALL TextLayout::getCaret( sal_Int32 /*nInsertionIndex*/, + sal_Bool /*bExcludeLigatures*/ ) + { + // TODO + return rendering::Caret(); + } + + sal_Int32 SAL_CALL TextLayout::getNextInsertionIndex( sal_Int32 /*nStartIndex*/, + sal_Int32 /*nCaretAdvancement*/, + sal_Bool /*bExcludeLigatures*/ ) + { + // TODO + return 0; + } + + uno::Reference< rendering::XPolyPolygon2D > SAL_CALL TextLayout::queryVisualHighlighting( sal_Int32 /*nStartIndex*/, + sal_Int32 /*nEndIndex*/ ) + { + // TODO + return uno::Reference< rendering::XPolyPolygon2D >(); + } + + uno::Reference< rendering::XPolyPolygon2D > SAL_CALL TextLayout::queryLogicalHighlighting( sal_Int32 /*nStartIndex*/, + sal_Int32 /*nEndIndex*/ ) + { + // TODO + return uno::Reference< rendering::XPolyPolygon2D >(); + } + + double SAL_CALL TextLayout::getBaselineOffset( ) + { + // TODO + return 0.0; + } + + sal_Int8 SAL_CALL TextLayout::getMainTextDirection( ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + + return mnTextDirection; + } + + uno::Reference< rendering::XCanvasFont > SAL_CALL TextLayout::getFont( ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + + return mpFont; + } + + rendering::StringContext SAL_CALL TextLayout::getText( ) + { + ::osl::MutexGuard aGuard( m_aMutex ); + + return maText; + } + + bool TextLayout::draw( const GraphicsSharedPtr& rGraphics, + const rendering::ViewState& rViewState, + const rendering::RenderState& rRenderState, + const ::basegfx::B2ISize& rOutputOffset, + const uno::Reference< rendering::XGraphicDevice >& xGraphicDevice, + bool bAlphaSurface ) const + { + ::osl::MutexGuard aGuard( m_aMutex ); + + ::dxcanvas::TextLayoutDrawHelper aDrawHelper(xGraphicDevice); + + // render text + aDrawHelper.drawText( + rGraphics, + rViewState, + rRenderState, + rOutputOffset, + maText, + maLogicalAdvancements, + mpFont, + mpFont->getFontMatrix(), + bAlphaSurface, + mnTextDirection != 0); + + return true; + } + + OUString SAL_CALL TextLayout::getImplementationName() + { + return "DXCanvas::TextLayout"; + } + + sal_Bool SAL_CALL TextLayout::supportsService( const OUString& ServiceName ) + { + return cppu::supportsService( this, ServiceName ); + } + + uno::Sequence< OUString > SAL_CALL TextLayout::getSupportedServiceNames() + { + return { "com.sun.star.rendering.TextLayout" }; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_textlayout.hxx b/canvas/source/directx/dx_textlayout.hxx new file mode 100644 index 000000000..24d37e210 --- /dev/null +++ b/canvas/source/directx/dx_textlayout.hxx @@ -0,0 +1,104 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include <cppuhelper/compbase.hxx> +#include <cppuhelper/basemutex.hxx> + +#include <com/sun/star/lang/XServiceInfo.hpp> + +#include <com/sun/star/rendering/XTextLayout.hpp> + +#include <basegfx/vector/b2isize.hxx> + +#include "dx_canvasfont.hxx" +#include "dx_ibitmap.hxx" +#include "dx_winstuff.hxx" +#include "dx_gdiplususer.hxx" + + +/* Definition of TextLayout class */ +namespace dxcanvas +{ + typedef ::cppu::WeakComponentImplHelper< css::rendering::XTextLayout, + css::lang::XServiceInfo > TextLayout_Base; + + class TextLayout : public ::cppu::BaseMutex, + public TextLayout_Base + { + public: + TextLayout( const css::rendering::StringContext& aText, + sal_Int8 nDirection, + sal_Int64 nRandomSeed, + const CanvasFont::ImplRef& rFont ); + /// make noncopyable + TextLayout(const TextLayout&) = delete; + const TextLayout& operator=(const TextLayout&) = delete; + + /// Dispose all internal references + virtual void SAL_CALL disposing() override; + + // XTextLayout + virtual css::uno::Sequence< css::uno::Reference< css::rendering::XPolyPolygon2D > > SAL_CALL queryTextShapes( ) override; + virtual css::uno::Sequence< css::geometry::RealRectangle2D > SAL_CALL queryInkMeasures( ) override; + virtual css::uno::Sequence< css::geometry::RealRectangle2D > SAL_CALL queryMeasures( ) override; + virtual css::uno::Sequence< double > SAL_CALL queryLogicalAdvancements( ) override; + virtual void SAL_CALL applyLogicalAdvancements( const css::uno::Sequence< double >& aAdvancements ) override; + virtual css::geometry::RealRectangle2D SAL_CALL queryTextBounds( ) override; + virtual double SAL_CALL justify( double nSize ) override; + virtual double SAL_CALL combinedJustify( const css::uno::Sequence< css::uno::Reference< css::rendering::XTextLayout > >& aNextLayouts, double nSize ) override; + virtual css::rendering::TextHit SAL_CALL getTextHit( const css::geometry::RealPoint2D& aHitPoint ) override; + virtual css::rendering::Caret SAL_CALL getCaret( sal_Int32 nInsertionIndex, sal_Bool bExcludeLigatures ) override; + virtual sal_Int32 SAL_CALL getNextInsertionIndex( sal_Int32 nStartIndex, sal_Int32 nCaretAdvancement, sal_Bool bExcludeLigatures ) override; + virtual css::uno::Reference< css::rendering::XPolyPolygon2D > SAL_CALL queryVisualHighlighting( sal_Int32 nStartIndex, sal_Int32 nEndIndex ) override; + virtual css::uno::Reference< css::rendering::XPolyPolygon2D > SAL_CALL queryLogicalHighlighting( sal_Int32 nStartIndex, sal_Int32 nEndIndex ) override; + virtual double SAL_CALL getBaselineOffset( ) override; + virtual sal_Int8 SAL_CALL getMainTextDirection( ) override; + virtual css::uno::Reference< css::rendering::XCanvasFont > SAL_CALL getFont( ) override; + virtual css::rendering::StringContext SAL_CALL getText( ) override; + + // XServiceInfo + virtual OUString SAL_CALL getImplementationName() override; + virtual sal_Bool SAL_CALL supportsService( const OUString& ServiceName ) override; + virtual css::uno::Sequence< OUString > SAL_CALL getSupportedServiceNames() override; + + bool draw( const GraphicsSharedPtr& rGraphics, + const css::rendering::ViewState& rViewState, + const css::rendering::RenderState& rRenderState, + const ::basegfx::B2ISize& rOutputOffset, + const css::uno::Reference< + css::rendering::XGraphicDevice >& xGraphicDevice, + bool bAlphaSurface ) const; + + protected: + ~TextLayout() override; // we're a ref-counted UNO class. _We_ destroy ourselves. + + private: + // NOTE: no need for GDIPlusUserSharedPtr, mpFont implicitly has one already + + css::rendering::StringContext maText; + css::uno::Sequence< double > maLogicalAdvancements; + CanvasFont::ImplRef mpFont; + sal_Int8 mnTextDirection; + }; + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_textlayout_drawhelper.cxx b/canvas/source/directx/dx_textlayout_drawhelper.cxx new file mode 100644 index 000000000..20ff8bd44 --- /dev/null +++ b/canvas/source/directx/dx_textlayout_drawhelper.cxx @@ -0,0 +1,311 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <memory> + +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/polygon/b2dpolypolygon.hxx> +#include <basegfx/utils/canvastools.hxx> +#include <com/sun/star/rendering/FontRequest.hpp> +#include <com/sun/star/rendering/PanoseProportion.hpp> +#include <com/sun/star/rendering/XCanvasFont.hpp> +#include <comphelper/scopeguard.hxx> +#include <i18nlangtag/languagetag.hxx> +#include <rtl/math.hxx> +#include <tools/color.hxx> +#include <tools/diagnose_ex.h> +#include <tools/poly.hxx> +#include <vcl/canvastools.hxx> +#include <vcl/metric.hxx> +#include <vcl/sysdata.hxx> +#include <vcl/virdev.hxx> + +#include <canvas/canvastools.hxx> + +#include "dx_bitmap.hxx" +#include "dx_canvasfont.hxx" +#include "dx_impltools.hxx" +#include "dx_textlayout_drawhelper.hxx" + +using namespace ::com::sun::star; + + +namespace dxcanvas +{ + TextLayoutDrawHelper::TextLayoutDrawHelper( + const uno::Reference< rendering::XGraphicDevice >& xGraphicDevice ) : + mxGraphicDevice(xGraphicDevice) + { + } + + TextLayoutDrawHelper::~TextLayoutDrawHelper() + { + } + + void TextLayoutDrawHelper::drawText( + const std::shared_ptr<Gdiplus::Graphics>& rGraphics, + const css::rendering::ViewState& rViewState, + const css::rendering::RenderState& rRenderState, + const ::basegfx::B2ISize& rOutputOffset, + const css::rendering::StringContext& rText, + const css::uno::Sequence< double >& rLogicalAdvancements, + const css::uno::Reference< + css::rendering::XCanvasFont >& rCanvasFont, + const css::geometry::Matrix2D& rFontMatrix, + bool bAlphaSurface, + bool bIsRTL) + { + HDC hdc = rGraphics->GetHDC(); + + // issue a ReleaseHDC() when leaving the scope + const ::comphelper::ScopeGuard aGuard( + [&rGraphics, &hdc]() mutable { rGraphics->ReleaseHDC(hdc); } ); + + SystemGraphicsData aSystemGraphicsData; + aSystemGraphicsData.nSize = sizeof(SystemGraphicsData); + aSystemGraphicsData.hDC = reinterpret_cast< ::HDC >(hdc); + ScopedVclPtrInstance<VirtualDevice> xVirtualDevice(aSystemGraphicsData, Size(1, 1), DeviceFormat::DEFAULT); + + // disable font antialiasing - GDI does not handle alpha + // surfaces properly. + if( bAlphaSurface ) + xVirtualDevice->SetAntialiasing(AntialiasingFlags::DisableText); + + if(rText.Length) + { + bool test = mxGraphicDevice.is(); + ENSURE_OR_THROW( test, + "TextLayoutDrawHelper::drawText(): Invalid GraphicDevice" ); + + // set text color. Make sure to remove transparence part first. + Color aColor( COL_WHITE ); + + if( rRenderState.DeviceColor.getLength() > 2 ) + aColor = vcl::unotools::doubleSequenceToColor( + rRenderState.DeviceColor, + mxGraphicDevice->getDeviceColorSpace()); + aColor.SetAlpha(255); + xVirtualDevice->SetTextColor(aColor); + + // create the font + const css::rendering::FontRequest& rFontRequest = rCanvasFont->getFontRequest(); + vcl::Font aFont( + rFontRequest.FontDescription.FamilyName, + rFontRequest.FontDescription.StyleName, + Size( 0, ::basegfx::fround(rFontRequest.CellSize))); + + aFont.SetAlignment( ALIGN_BASELINE ); + aFont.SetCharSet( (rFontRequest.FontDescription.IsSymbolFont==css::util::TriState_YES) ? RTL_TEXTENCODING_SYMBOL : RTL_TEXTENCODING_UNICODE ); + aFont.SetVertical( rFontRequest.FontDescription.IsVertical==css::util::TriState_YES ); + aFont.SetWeight( static_cast<FontWeight>(rFontRequest.FontDescription.FontDescription.Weight) ); + aFont.SetItalic( (rFontRequest.FontDescription.FontDescription.Letterform<=8) ? ITALIC_NONE : ITALIC_NORMAL ); + aFont.SetPitch( + rFontRequest.FontDescription.FontDescription.Proportion == rendering::PanoseProportion::MONO_SPACED + ? PITCH_FIXED : PITCH_VARIABLE); + + aFont.SetLanguage(LanguageTag::convertToLanguageType(rFontRequest.Locale)); + + // setup font color + aFont.SetColor( aColor ); + aFont.SetFillColor( aColor ); + + CanvasFont::ImplRef pFont(tools::canvasFontFromXFont(rCanvasFont)); + if (pFont.is() && pFont->getEmphasisMark()) + aFont.SetEmphasisMark(FontEmphasisMark(pFont->getEmphasisMark())); + + // adjust to stretched font + if(!::rtl::math::approxEqual(rFontMatrix.m00, rFontMatrix.m11)) + { + const Size aSize = xVirtualDevice->GetFontMetric( aFont ).GetFontSize(); + const double fDividend( rFontMatrix.m10 + rFontMatrix.m11 ); + double fStretch = rFontMatrix.m00 + rFontMatrix.m01; + + if( !::basegfx::fTools::equalZero( fDividend) ) + fStretch /= fDividend; + + const sal_Int32 nNewWidth = ::basegfx::fround( aSize.Width() * fStretch ); + + aFont.SetAverageFontWidth( nNewWidth ); + } + + // set font + xVirtualDevice->SetFont(aFont); + + // create world transformation matrix + ::basegfx::B2DHomMatrix aWorldTransform; + ::canvas::tools::mergeViewAndRenderTransform(aWorldTransform, rViewState, rRenderState); + + if(!rOutputOffset.equalZero()) + { + aWorldTransform.translate(rOutputOffset.getX(), rOutputOffset.getY()); + } + + // set ViewState clipping + if(rViewState.Clip.is()) + { + ::basegfx::B2DPolyPolygon aClipPoly(dxcanvas::tools::polyPolygonFromXPolyPolygon2D(rViewState.Clip)); + ::basegfx::B2DHomMatrix aMatrix; + ::basegfx::unotools::homMatrixFromAffineMatrix(aMatrix, rViewState.AffineTransform ); + + if(!rOutputOffset.equalZero()) + { + aMatrix.translate(rOutputOffset.getX(), rOutputOffset.getY()); + } + + aClipPoly.transform(aMatrix); + const vcl::Region& rClipRegion = vcl::Region(::tools::PolyPolygon(aClipPoly)); + xVirtualDevice->IntersectClipRegion(rClipRegion); + } + + if(rRenderState.Clip.is()) + { + ::basegfx::B2DPolyPolygon aClipPoly(dxcanvas::tools::polyPolygonFromXPolyPolygon2D(rRenderState.Clip)); + aClipPoly.transform(aWorldTransform); + const vcl::Region& rClipRegion = vcl::Region(::tools::PolyPolygon(aClipPoly)); + xVirtualDevice->IntersectClipRegion(rClipRegion); + } + + // set world transform + XFORM aXForm; + aXForm.eM11 = static_cast<FLOAT>(aWorldTransform.get(0, 0)); + aXForm.eM12 = static_cast<FLOAT>(aWorldTransform.get(1, 0)); + aXForm.eM21 = static_cast<FLOAT>(aWorldTransform.get(0, 1)); + aXForm.eM22 = static_cast<FLOAT>(aWorldTransform.get(1, 1)); + aXForm.eDx = static_cast<FLOAT>(aWorldTransform.get(0, 2)); + aXForm.eDy = static_cast<FLOAT>(aWorldTransform.get(1, 2)); + + // TODO(F3): This is NOT supported on 95/98/ME! + SetGraphicsMode(hdc, GM_ADVANCED); + SetTextAlign(hdc, TA_BASELINE); + SetWorldTransform(hdc, &aXForm); + + // use an empty StartPosition for text rendering + const Point aEmptyPoint(0, 0); + + // create the String + const OUString aText(rText.Text); + + if( rLogicalAdvancements.getLength() ) + { + // create the DXArray + const sal_Int32 nLen( rLogicalAdvancements.getLength() ); + std::vector<sal_Int32> DXArray( nLen ); + for( sal_Int32 i=0; i<nLen; ++i ) + DXArray[i] = basegfx::fround( rLogicalAdvancements[i] ); + + // draw the String + xVirtualDevice->DrawTextArray( aEmptyPoint, + aText, + DXArray, + rText.StartPosition, + rText.Length, + bIsRTL ? SalLayoutFlags::BiDiRtl : SalLayoutFlags::NONE); + } + else + { + // draw the String + xVirtualDevice->DrawText( aEmptyPoint, + aText, + rText.StartPosition, + rText.Length ); + } + } + } + + geometry::RealRectangle2D TextLayoutDrawHelper::queryTextBounds( const rendering::StringContext& rText, + const uno::Sequence< double >& rLogicalAdvancements, + const uno::Reference< rendering::XCanvasFont >& rCanvasFont, + const geometry::Matrix2D& rFontMatrix ) + { + if(!(rText.Length)) + return geometry::RealRectangle2D(); + + // TODO(F1): Fetching default screen DC here, will yield wrong + // metrics when e.g. formatting for a printer! + SystemGraphicsData aSystemGraphicsData; + aSystemGraphicsData.nSize = sizeof(SystemGraphicsData); + aSystemGraphicsData.hDC = reinterpret_cast< ::HDC >(GetDC( nullptr )); + ScopedVclPtrInstance<VirtualDevice> xVirtualDevice(aSystemGraphicsData, Size(1, 1), DeviceFormat::DEFAULT); + + // create the font + const css::rendering::FontRequest& rFontRequest = rCanvasFont->getFontRequest(); + vcl::Font aFont( + rFontRequest.FontDescription.FamilyName, + rFontRequest.FontDescription.StyleName, + Size( 0, ::basegfx::fround(rFontRequest.CellSize))); + + aFont.SetAlignment( ALIGN_BASELINE ); + aFont.SetCharSet( (rFontRequest.FontDescription.IsSymbolFont==css::util::TriState_YES) ? RTL_TEXTENCODING_SYMBOL : RTL_TEXTENCODING_UNICODE ); + aFont.SetVertical( rFontRequest.FontDescription.IsVertical==css::util::TriState_YES ); + aFont.SetWeight( static_cast<FontWeight>(rFontRequest.FontDescription.FontDescription.Weight) ); + aFont.SetItalic( (rFontRequest.FontDescription.FontDescription.Letterform<=8) ? ITALIC_NONE : ITALIC_NORMAL ); + aFont.SetPitch( + rFontRequest.FontDescription.FontDescription.Proportion == rendering::PanoseProportion::MONO_SPACED + ? PITCH_FIXED : PITCH_VARIABLE); + + // adjust to stretched font + if(!::rtl::math::approxEqual(rFontMatrix.m00, rFontMatrix.m11)) + { + const Size aSize = xVirtualDevice->GetFontMetric( aFont ).GetFontSize(); + const double fDividend( rFontMatrix.m10 + rFontMatrix.m11 ); + double fStretch = rFontMatrix.m00 + rFontMatrix.m01; + + if( !::basegfx::fTools::equalZero( fDividend) ) + fStretch /= fDividend; + + const sal_Int32 nNewWidth = ::basegfx::fround( aSize.Width() * fStretch ); + + aFont.SetAverageFontWidth( nNewWidth ); + } + + CanvasFont::ImplRef pFont(tools::canvasFontFromXFont(rCanvasFont)); + if (pFont.is() && pFont->getEmphasisMark()) + aFont.SetEmphasisMark(FontEmphasisMark(pFont->getEmphasisMark())); + + // set font + xVirtualDevice->SetFont(aFont); + + // need metrics for Y offset, the XCanvas always renders + // relative to baseline + const ::FontMetric& aMetric( xVirtualDevice->GetFontMetric() ); + + const sal_Int32 nAboveBaseline( -aMetric.GetInternalLeading() - aMetric.GetAscent() ); + const sal_Int32 nBelowBaseline( aMetric.GetDescent() ); + + if( rLogicalAdvancements.getLength() ) + { + return geometry::RealRectangle2D( 0, nAboveBaseline, + rLogicalAdvancements[ rLogicalAdvancements.getLength()-1 ], + nBelowBaseline ); + } + else + { + return geometry::RealRectangle2D( 0, nAboveBaseline, + xVirtualDevice->GetTextWidth( + rText.Text, + ::canvas::tools::numeric_cast<sal_uInt16>(rText.StartPosition), + ::canvas::tools::numeric_cast<sal_uInt16>(rText.Length) ), + nBelowBaseline ); + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_textlayout_drawhelper.hxx b/canvas/source/directx/dx_textlayout_drawhelper.hxx new file mode 100644 index 000000000..0db8fca69 --- /dev/null +++ b/canvas/source/directx/dx_textlayout_drawhelper.hxx @@ -0,0 +1,77 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include <memory> + +#include <com/sun/star/uno/Sequence.hxx> +#include <com/sun/star/rendering/StringContext.hpp> +#include <com/sun/star/rendering/XCanvasFont.hpp> +#include <com/sun/star/geometry/Matrix2D.hpp> +#include <com/sun/star/rendering/XGraphicDevice.hpp> +#include <com/sun/star/rendering/ViewState.hpp> +#include <com/sun/star/rendering/RenderState.hpp> + +#include <basegfx/vector/b2isize.hxx> + +namespace com::sun::star::rendering { class XCanvasFont; } + +namespace Gdiplus { class Graphics; } + +namespace dxcanvas +{ + struct Bitmap; + class TextLayoutDrawHelper + { + public: + explicit TextLayoutDrawHelper( + const css::uno::Reference< css::rendering::XGraphicDevice >& xGraphicDevice); + ~TextLayoutDrawHelper(); + + // draw text + void drawText( const std::shared_ptr<Gdiplus::Graphics>& rGraphics, + const css::rendering::ViewState& rViewState, + const css::rendering::RenderState& rRenderState, + const ::basegfx::B2ISize& rOutputOffset, + const css::rendering::StringContext& rText, + const css::uno::Sequence< double >& rLogicalAdvancements, + const css::uno::Reference< + css::rendering::XCanvasFont >& rCanvasFont, + const css::geometry::Matrix2D& rFontMatrix, + bool bAlphaSurface, + bool bIsRTL); + + css::geometry::RealRectangle2D queryTextBounds( + const css::rendering::StringContext& rText, + const css::uno::Sequence< double >& rLogicalAdvancements, + const css::uno::Reference< + css::rendering::XCanvasFont >& rCanvasFont, + const css::geometry::Matrix2D& rFontMatrix ); + +#ifdef DBG_UTIL + void test(); +#endif + + protected: + css::uno::Reference< css::rendering::XGraphicDevice > mxGraphicDevice; + }; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_vcltools.cxx b/canvas/source/directx/dx_vcltools.cxx new file mode 100644 index 000000000..1544ec916 --- /dev/null +++ b/canvas/source/directx/dx_vcltools.cxx @@ -0,0 +1,322 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <basegfx/numeric/ftools.hxx> +#include <com/sun/star/lang/XServiceInfo.hpp> +#include <com/sun/star/rendering/XIntegerBitmap.hpp> +#include <tools/diagnose_ex.h> +#include <vcl/bitmap.hxx> +#include <vcl/bitmapex.hxx> +#include <vcl/BitmapReadAccess.hxx> +#include <vcl/canvastools.hxx> + +#include "dx_impltools.hxx" +#include "dx_vcltools.hxx" + +using namespace ::com::sun::star; + +namespace dxcanvas::tools +{ + namespace + { + /// Calc number of colors in given BitmapInfoHeader + sal_Int32 calcDIBColorCount( const BITMAPINFOHEADER& rBIH ) + { + if( rBIH.biSize != sizeof( BITMAPCOREHEADER ) ) + { + if( rBIH.biBitCount <= 8 ) + { + if( rBIH.biClrUsed ) + return rBIH.biClrUsed; + else + return 1 << rBIH.biBitCount; + } + } + else + { + BITMAPCOREHEADER const * pCoreHeader = reinterpret_cast<BITMAPCOREHEADER const *>(&rBIH); + + if( pCoreHeader->bcBitCount <= 8 ) + return 1 << pCoreHeader->bcBitCount; + } + + return 0; // nothing known + } + + /// Draw DI bits to given Graphics + bool drawDIBits( const std::shared_ptr< Gdiplus::Graphics >& rGraphics, + const void* hDIB ) + { + bool bRet( false ); + + const BITMAPINFO* pBI = static_cast<BITMAPINFO*>(GlobalLock( const_cast<void *>(hDIB) )); + + if( pBI ) + { + const BYTE* pBits = reinterpret_cast<BYTE const *>(pBI) + pBI->bmiHeader.biSize + + calcDIBColorCount( pBI->bmiHeader ) * sizeof( RGBQUAD ); + + // forward to outsourced GDI+ rendering method + // (header clashes) + bRet = tools::drawDIBits( rGraphics, *pBI, pBits ); + + GlobalUnlock( const_cast<void *>(hDIB) ); + } + + return bRet; + } + + /** Draw VCL bitmap to given Graphics + + @param rBmp + Reference to bitmap. Might get modified, in such a way + that it will hold a DIB after a successful function call. + */ + bool drawVCLBitmap( const std::shared_ptr< Gdiplus::Graphics >& rGraphics, + ::Bitmap& rBmp ) + { + BitmapSystemData aBmpSysData; + + if( !rBmp.GetSystemData( aBmpSysData ) || + !aBmpSysData.pDIB ) + { + // first of all, ensure that Bitmap contains a DIB, by + // acquiring a read access + BitmapReadAccess* pReadAcc = rBmp.AcquireReadAccess(); + + // TODO(P2): Acquiring a read access can actually + // force a read from VRAM, thus, avoiding this + // step somehow will increase performance + // here. + if( pReadAcc ) + { + // try again: now, WinSalBitmap must have + // generated a DIB + if( rBmp.GetSystemData( aBmpSysData ) && + aBmpSysData.pDIB ) + { + return drawDIBits( rGraphics, + aBmpSysData.pDIB ); + } + + Bitmap::ReleaseAccess( pReadAcc ); + } + } + else + { + return drawDIBits( rGraphics, + aBmpSysData.pDIB ); + } + + // failed to generate DIBits from vcl bitmap + return false; + } + + /** Create a chunk of raw RGBA data GDI+ Bitmap from VCL BitmapEx + */ + RawRGBABitmap bitmapFromVCLBitmapEx( const ::BitmapEx& rBmpEx ) + { + // TODO(P2): Avoid temporary bitmap generation, maybe + // even ensure that created DIBs are copied back to + // BmpEx (currently, every AcquireReadAccess() will + // make the local bitmap copy unique, effectively + // duplicating the memory used) + + ENSURE_OR_THROW( rBmpEx.IsAlpha(), + "::dxcanvas::tools::bitmapFromVCLBitmapEx(): " + "BmpEx has no alpha" ); + + // convert transparent bitmap to 32bit RGBA + // ======================================== + + const ::Size aBmpSize( rBmpEx.GetSizePixel() ); + + RawRGBABitmap aBmpData; + aBmpData.mnWidth = aBmpSize.Width(); + aBmpData.mnHeight = aBmpSize.Height(); + aBmpData.maBitmapData.resize(4*aBmpData.mnWidth*aBmpData.mnHeight); + + Bitmap aBitmap( rBmpEx.GetBitmap() ); + + Bitmap::ScopedReadAccess pReadAccess( aBitmap ); + + const sal_Int32 nWidth( aBmpSize.Width() ); + const sal_Int32 nHeight( aBmpSize.Height() ); + + ENSURE_OR_THROW( pReadAccess.get() != nullptr, + "::dxcanvas::tools::bitmapFromVCLBitmapEx(): " + "Unable to acquire read access to bitmap" ); + + Bitmap aAlpha( rBmpEx.GetAlpha().GetBitmap() ); + + Bitmap::ScopedReadAccess pAlphaReadAccess( aAlpha ); + + // By convention, the access buffer always has + // one of the following formats: + + // ScanlineFormat::N1BitMsbPal + // ScanlineFormat::N8BitPal + // ScanlineFormat::N24BitTcBgr + // ScanlineFormat::N32BitTcMask + + // and is always ScanlineFormat::BottomUp + + // This is the way + // WinSalBitmap::AcquireBuffer() sets up the + // buffer + + ENSURE_OR_THROW( pAlphaReadAccess.get() != nullptr, + "::dxcanvas::tools::bitmapFromVCLBitmapEx(): " + "Unable to acquire read access to alpha" ); + + ENSURE_OR_THROW( pAlphaReadAccess->GetScanlineFormat() == ScanlineFormat::N8BitPal, + "::dxcanvas::tools::bitmapFromVCLBitmapEx(): " + "Unsupported alpha scanline format" ); + + BitmapColor aCol; + sal_uInt8* pCurrOutput(aBmpData.maBitmapData.data()); + int x, y; + + for( y=0; y<nHeight; ++y ) + { + switch( pReadAccess->GetScanlineFormat() ) + { + case ScanlineFormat::N8BitPal: + { + Scanline pScan = pReadAccess->GetScanline( y ); + Scanline pAScan = pAlphaReadAccess->GetScanline( y ); + + for( x=0; x<nWidth; ++x ) + { + aCol = pReadAccess->GetPaletteColor( *pScan++ ); + + *pCurrOutput++ = aCol.GetBlue(); + *pCurrOutput++ = aCol.GetGreen(); + *pCurrOutput++ = aCol.GetRed(); + + // our notion of alpha is + // different from the rest + // of the world's + *pCurrOutput++ = 255 - static_cast<BYTE>(*pAScan++); + } + } + break; + + case ScanlineFormat::N24BitTcBgr: + { + Scanline pScan = pReadAccess->GetScanline( y ); + Scanline pAScan = pAlphaReadAccess->GetScanline( y ); + + for( x=0; x<nWidth; ++x ) + { + // store as RGBA + *pCurrOutput++ = *pScan++; + *pCurrOutput++ = *pScan++; + *pCurrOutput++ = *pScan++; + + // our notion of alpha is + // different from the rest + // of the world's + *pCurrOutput++ = 255 - static_cast<BYTE>(*pAScan++); + } + } + break; + + // TODO(P2): Might be advantageous + // to hand-formulate the following + // formats, too. + case ScanlineFormat::N1BitMsbPal: + case ScanlineFormat::N32BitTcMask: + { + Scanline pAScan = pAlphaReadAccess->GetScanline( y ); + + // using fallback for those + // seldom formats + for( x=0; x<nWidth; ++x ) + { + // yes. x and y are swapped on Get/SetPixel + aCol = pReadAccess->GetColor(y,x); + + *pCurrOutput++ = aCol.GetBlue(); + *pCurrOutput++ = aCol.GetGreen(); + *pCurrOutput++ = aCol.GetRed(); + + // our notion of alpha is + // different from the rest + // of the world's + *pCurrOutput++ = 255 - static_cast<BYTE>(*pAScan++); + } + } + break; + + case ScanlineFormat::N1BitLsbPal: + case ScanlineFormat::N24BitTcRgb: + case ScanlineFormat::N32BitTcAbgr: + case ScanlineFormat::N32BitTcArgb: + case ScanlineFormat::N32BitTcBgra: + case ScanlineFormat::N32BitTcRgba: + default: + ENSURE_OR_THROW( false, + "::dxcanvas::tools::bitmapFromVCLBitmapEx(): " + "Unexpected scanline format - has " + "WinSalBitmap::AcquireBuffer() changed?" ); + } + } + + return aBmpData; + } + + bool drawVCLBitmapEx( const std::shared_ptr< Gdiplus::Graphics >& rGraphics, + const ::BitmapEx& rBmpEx ) + { + if( !rBmpEx.IsAlpha() ) + { + Bitmap aBmp( rBmpEx.GetBitmap() ); + return drawVCLBitmap( rGraphics, aBmp ); + } + else + { + return drawRGBABits( rGraphics, + bitmapFromVCLBitmapEx( rBmpEx ) ); + } + } + } + + bool drawVCLBitmapFromXBitmap( const std::shared_ptr< Gdiplus::Graphics >& rGraphics, + const uno::Reference< rendering::XBitmap >& xBitmap ) + { + // TODO(F2): add support for floating point bitmap formats + uno::Reference< rendering::XIntegerReadOnlyBitmap > xIntBmp( + xBitmap, uno::UNO_QUERY ); + + if( !xIntBmp.is() ) + return false; + + ::BitmapEx aBmpEx = vcl::unotools::bitmapExFromXBitmap( xIntBmp ); + if( aBmpEx.IsEmpty() ) + return false; + + return drawVCLBitmapEx( rGraphics, aBmpEx ); + } +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_vcltools.hxx b/canvas/source/directx/dx_vcltools.hxx new file mode 100644 index 000000000..433afa618 --- /dev/null +++ b/canvas/source/directx/dx_vcltools.hxx @@ -0,0 +1,47 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include <com/sun/star/uno/Reference.hxx> +#include <com/sun/star/rendering/XBitmap.hpp> +#include <memory> +#include <vector> + +namespace Gdiplus { class Graphics; } + +namespace dxcanvas::tools +{ + /** Raw RGBA bitmap data, + contiguous in memory + */ + struct RawRGBABitmap + { + sal_Int32 mnWidth; + sal_Int32 mnHeight; + std::vector<sal_uInt8> maBitmapData; + }; + + bool drawVCLBitmapFromXBitmap( const std::shared_ptr< Gdiplus::Graphics >& rGraphics, + const css::uno::Reference< + css::rendering::XBitmap >& xBitmap ); +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/dx_winstuff.hxx b/canvas/source/directx/dx_winstuff.hxx new file mode 100644 index 000000000..4cd0007fa --- /dev/null +++ b/canvas/source/directx/dx_winstuff.hxx @@ -0,0 +1,71 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + +#include <algorithm> +#include <memory> + +#include <basegfx/numeric/ftools.hxx> + +#include <prewin.h> + +// Enabling Direct3D Debug Information Further more, with registry key +// \\HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Direct3D\D3D9Debugging\\EnableCreationStack +// set to 1, sets a backtrace each time an object is created to the +// following global variable: LPCWSTR CreationCallStack +#if OSL_DEBUG_LEVEL > 0 +# define D3D_DEBUG_INFO +#endif + +#include <d3d9.h> + +typedef IDirect3DSurface9 surface_type; + + +#ifndef max +#define max(a,b) (((a) > (b)) ? (a) : (b)) +#endif +#ifndef min +#define min(a,b) (((a) < (b)) ? (a) : (b)) +#endif + +#include <gdiplus.h> + +#undef max +#undef min + + +namespace dxcanvas +{ + // some shared pointer typedefs to Gdiplus objects + typedef std::shared_ptr< Gdiplus::Graphics > GraphicsSharedPtr; + typedef std::shared_ptr< Gdiplus::GraphicsPath > GraphicsPathSharedPtr; + typedef std::shared_ptr< Gdiplus::Bitmap > BitmapSharedPtr; + typedef std::shared_ptr< Gdiplus::Font > FontSharedPtr; + typedef std::shared_ptr< Gdiplus::TextureBrush > TextureBrushSharedPtr; +} + +#include <systools/win32/comtools.hxx> // for COMReference; must be inside prewin...postwin +// Attention! All DirectX factory methods return the created interfaces pre-acquired, into a raw +// pointer. Do not call AddRef on them when constructing COMReference! + +#include <postwin.h> + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/canvas/source/directx/gdipluscanvas.component b/canvas/source/directx/gdipluscanvas.component new file mode 100644 index 000000000..0ee68a1ad --- /dev/null +++ b/canvas/source/directx/gdipluscanvas.component @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + --> + +<component loader="com.sun.star.loader.SharedLibrary" environment="@CPPU_ENV@" + xmlns="http://openoffice.org/2010/uno-components"> + <implementation name="com.sun.star.comp.rendering.BitmapCanvas.GDI+" + constructor="canvas_gdiplus_BitmapCanvas_get_implementation"> + <service name="com.sun.star.rendering.BitmapCanvas.GDI+"/> + </implementation> + <implementation name="com.sun.star.comp.rendering.Canvas.GDI+" + constructor="canvas_gdiplus_Canvas_get_implementation"> + <service name="com.sun.star.rendering.Canvas.GDI+"/> + </implementation> +</component> |