/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * This file incorporates work covered by the following license notice: * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed * with this work for additional information regarding copyright * ownership. The ASF licenses this file to you under the Apache * License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.apache.org/licenses/LICENSE-2.0 . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if ENABLE_CAIRO_CANVAS # if defined CAIRO_VERSION && CAIRO_VERSION < CAIRO_VERSION_ENCODE(1, 10, 0) # define CAIRO_OPERATOR_DIFFERENCE (static_cast(23)) # endif #endif namespace { // MM02 decide to use buffers or not const char* pDisableMM02Goodies(getenv("SAL_DISABLE_MM02_GOODIES")); bool bUseBuffer(nullptr == pDisableMM02Goodies); const tools::Long nMinimalSquareSizeToBuffer(64*64); void tryToUseSourceBuffer(const SalBitmap& rSourceBitmap, std::shared_ptr& rSurface) { // MM02 try to access buffered BitmapHelper std::shared_ptr pSystemDependentData_BitmapHelper; const bool bBufferSource(bUseBuffer && rSourceBitmap.GetSize().Width() * rSourceBitmap.GetSize().Height() > nMinimalSquareSizeToBuffer); if(bBufferSource) { const SvpSalBitmap& rSrcBmp(static_cast(rSourceBitmap)); pSystemDependentData_BitmapHelper = rSrcBmp.getSystemDependentData(); if(pSystemDependentData_BitmapHelper) { // reuse buffered data rSurface = pSystemDependentData_BitmapHelper->getBitmapHelper(); } } if(rSurface) return; // create data on-demand rSurface = std::make_shared(rSourceBitmap); if(bBufferSource) { // add to buffering mechanism to potentially reuse next time const SvpSalBitmap& rSrcBmp(static_cast(rSourceBitmap)); rSrcBmp.addOrReplaceSystemDependentData( ImplGetSystemDependentDataManager(), rSurface); } } void tryToUseMaskBuffer(const SalBitmap& rMaskBitmap, std::shared_ptr& rMask) { // MM02 try to access buffered MaskHelper std::shared_ptr pSystemDependentData_MaskHelper; const bool bBufferMask(bUseBuffer && rMaskBitmap.GetSize().Width() * rMaskBitmap.GetSize().Height() > nMinimalSquareSizeToBuffer); if(bBufferMask) { const SvpSalBitmap& rSrcBmp(static_cast(rMaskBitmap)); pSystemDependentData_MaskHelper = rSrcBmp.getSystemDependentData(); if(pSystemDependentData_MaskHelper) { // reuse buffered data rMask = pSystemDependentData_MaskHelper->getMaskHelper(); } } if(rMask) return; // create data on-demand rMask = std::make_shared(rMaskBitmap); if(bBufferMask) { // add to buffering mechanism to potentially reuse next time const SvpSalBitmap& rSrcBmp(static_cast(rMaskBitmap)); rSrcBmp.addOrReplaceSystemDependentData( ImplGetSystemDependentDataManager(), rMask); } } } bool SvpSalGraphics::drawAlphaBitmap( const SalTwoRect& rTR, const SalBitmap& rSourceBitmap, const SalBitmap& rAlphaBitmap ) { if (rAlphaBitmap.GetBitCount() != 8 && rAlphaBitmap.GetBitCount() != 1) { SAL_WARN("vcl.gdi", "unsupported SvpSalGraphics::drawAlphaBitmap alpha depth case: " << rAlphaBitmap.GetBitCount()); return false; } // MM02 try to access buffered BitmapHelper std::shared_ptr aSurface; tryToUseSourceBuffer(rSourceBitmap, aSurface); cairo_surface_t* source = aSurface->getSurface( rTR.mnDestWidth, rTR.mnDestHeight); if (!source) { SAL_WARN("vcl.gdi", "unsupported SvpSalGraphics::drawAlphaBitmap case"); return false; } // MM02 try to access buffered MaskHelper std::shared_ptr aMask; tryToUseMaskBuffer(rAlphaBitmap, aMask); cairo_surface_t *mask = aMask->getSurface( rTR.mnDestWidth, rTR.mnDestHeight); if (!mask) { SAL_WARN("vcl.gdi", "unsupported SvpSalGraphics::drawAlphaBitmap case"); return false; } cairo_t* cr = m_aCairoCommon.getCairoContext(false, getAntiAlias()); clipRegion(cr); cairo_rectangle(cr, rTR.mnDestX, rTR.mnDestY, rTR.mnDestWidth, rTR.mnDestHeight); basegfx::B2DRange extents = getClippedFillDamage(cr); cairo_clip(cr); cairo_pattern_t* maskpattern = cairo_pattern_create_for_surface(mask); cairo_translate(cr, rTR.mnDestX, rTR.mnDestY); double fXScale = static_cast(rTR.mnDestWidth)/rTR.mnSrcWidth; double fYScale = static_cast(rTR.mnDestHeight)/rTR.mnSrcHeight; cairo_scale(cr, fXScale, fYScale); cairo_set_source_surface(cr, source, -rTR.mnSrcX, -rTR.mnSrcY); //tdf#114117 when stretching a single pixel width/height source to fit an area //set extend and filter to stretch it with simplest expected interpolation if ((fXScale != 1.0 && rTR.mnSrcWidth == 1) || (fYScale != 1.0 && rTR.mnSrcHeight == 1)) { cairo_pattern_t* sourcepattern = cairo_get_source(cr); cairo_pattern_set_extend(sourcepattern, CAIRO_EXTEND_REPEAT); cairo_pattern_set_filter(sourcepattern, CAIRO_FILTER_NEAREST); cairo_pattern_set_extend(maskpattern, CAIRO_EXTEND_REPEAT); cairo_pattern_set_filter(maskpattern, CAIRO_FILTER_NEAREST); } //this block is just "cairo_mask_surface", but we have to make it explicit //because of the cairo_pattern_set_filter etc we may want applied cairo_matrix_t matrix; cairo_matrix_init_translate(&matrix, rTR.mnSrcX, rTR.mnSrcY); cairo_pattern_set_matrix(maskpattern, &matrix); cairo_mask(cr, maskpattern); cairo_pattern_destroy(maskpattern); m_aCairoCommon.releaseCairoContext(cr, false, extents); return true; } bool SvpSalGraphics::drawTransformedBitmap( const basegfx::B2DPoint& rNull, const basegfx::B2DPoint& rX, const basegfx::B2DPoint& rY, const SalBitmap& rSourceBitmap, const SalBitmap* pAlphaBitmap, double fAlpha) { if (pAlphaBitmap && pAlphaBitmap->GetBitCount() != 8 && pAlphaBitmap->GetBitCount() != 1) { SAL_WARN("vcl.gdi", "unsupported SvpSalGraphics::drawTransformedBitmap alpha depth case: " << pAlphaBitmap->GetBitCount()); return false; } if( fAlpha != 1.0 ) return false; // MM02 try to access buffered BitmapHelper std::shared_ptr aSurface; tryToUseSourceBuffer(rSourceBitmap, aSurface); const tools::Long nDestWidth(basegfx::fround(basegfx::B2DVector(rX - rNull).getLength())); const tools::Long nDestHeight(basegfx::fround(basegfx::B2DVector(rY - rNull).getLength())); cairo_surface_t* source( aSurface->getSurface( nDestWidth, nDestHeight)); if(!source) { SAL_WARN("vcl.gdi", "unsupported SvpSalGraphics::drawTransformedBitmap case"); return false; } // MM02 try to access buffered MaskHelper std::shared_ptr aMask; if(nullptr != pAlphaBitmap) { tryToUseMaskBuffer(*pAlphaBitmap, aMask); } // access cairo_surface_t from MaskHelper cairo_surface_t* mask(nullptr); if(aMask) { mask = aMask->getSurface( nDestWidth, nDestHeight); } if(nullptr != pAlphaBitmap && nullptr == mask) { SAL_WARN("vcl.gdi", "unsupported SvpSalGraphics::drawTransformedBitmap case"); return false; } const Size aSize = rSourceBitmap.GetSize(); cairo_t* cr = m_aCairoCommon.getCairoContext(false, getAntiAlias()); clipRegion(cr); // setup the image transformation // using the rNull,rX,rY points as destinations for the (0,0),(0,Width),(Height,0) source points const basegfx::B2DVector aXRel = rX - rNull; const basegfx::B2DVector aYRel = rY - rNull; cairo_matrix_t matrix; cairo_matrix_init(&matrix, aXRel.getX()/aSize.Width(), aXRel.getY()/aSize.Width(), aYRel.getX()/aSize.Height(), aYRel.getY()/aSize.Height(), rNull.getX(), rNull.getY()); cairo_transform(cr, &matrix); cairo_rectangle(cr, 0, 0, aSize.Width(), aSize.Height()); basegfx::B2DRange extents = getClippedFillDamage(cr); cairo_clip(cr); cairo_set_source_surface(cr, source, 0, 0); if (mask) cairo_mask_surface(cr, mask, 0, 0); else cairo_paint(cr); m_aCairoCommon.releaseCairoContext(cr, false, extents); return true; } SvpSalGraphics::SvpSalGraphics() : m_aTextRenderImpl(*this) , m_pBackend(new SvpGraphicsBackend(m_aCairoCommon)) { bool bLOKActive = comphelper::LibreOfficeKit::isActive(); initWidgetDrawBackends(bLOKActive); } SvpSalGraphics::~SvpSalGraphics() { ReleaseFonts(); } void SvpSalGraphics::setSurface(cairo_surface_t* pSurface, const basegfx::B2IVector& rSize) { m_aCairoCommon.m_pSurface = pSurface; m_aCairoCommon.m_aFrameSize = rSize; dl_cairo_surface_get_device_scale(pSurface, &m_aCairoCommon.m_fScale, nullptr); ResetClipRegion(); } void SvpSalGraphics::GetResolution( sal_Int32& rDPIX, sal_Int32& rDPIY ) { rDPIX = rDPIY = 96; } void SvpSalGraphics::drawBitmap(const SalTwoRect& rTR, const SalBitmap& rSourceBitmap) { // MM02 try to access buffered BitmapHelper std::shared_ptr aSurface; tryToUseSourceBuffer(rSourceBitmap, aSurface); cairo_surface_t* source = aSurface->getSurface( rTR.mnDestWidth, rTR.mnDestHeight); if (!source) { SAL_WARN("vcl.gdi", "unsupported SvpSalGraphics::drawAlphaBitmap case"); return; } #if 0 // LO code is not yet bitmap32-ready. // if m_bSupportsBitmap32 becomes true for Svp revisit this m_aCairoCommon.copyWithOperator(rTR, source, CAIRO_OPERATOR_OVER, getAntiAlias()); #else m_aCairoCommon.copyWithOperator(rTR, source, CAIRO_OPERATOR_SOURCE, getAntiAlias()); #endif } void SvpSalGraphics::drawBitmap(const SalTwoRect& rTR, const BitmapBuffer* pBuffer, cairo_operator_t eOp) { cairo_surface_t* source = CairoCommon::createCairoSurface(pBuffer); m_aCairoCommon.copyWithOperator(rTR, source, eOp, getAntiAlias()); cairo_surface_destroy(source); } void SvpSalGraphics::drawBitmap( const SalTwoRect& rTR, const SalBitmap& rSourceBitmap, const SalBitmap& rTransparentBitmap ) { drawAlphaBitmap(rTR, rSourceBitmap, rTransparentBitmap); } void SvpSalGraphics::drawMask( const SalTwoRect& rTR, const SalBitmap& rSalBitmap, Color nMaskColor ) { /** creates an image from the given rectangle, replacing all black pixels * with nMaskColor and make all other full transparent */ // MM02 here decided *against* using buffered BitmapHelper // because the data gets somehow 'unmuliplied'. This may also be // done just once, but I am not sure if this is safe to do. // So for now dispense re-using data here. BitmapHelper aSurface(rSalBitmap, true); // The mask is argb32 if (!aSurface.getSurface()) { SAL_WARN("vcl.gdi", "unsupported SvpSalGraphics::drawMask case"); return; } sal_Int32 nStride; unsigned char *mask_data = aSurface.getBits(nStride); vcl::bitmap::lookup_table const & unpremultiply_table = vcl::bitmap::get_unpremultiply_table(); for (tools::Long y = rTR.mnSrcY ; y < rTR.mnSrcY + rTR.mnSrcHeight; ++y) { unsigned char *row = mask_data + (nStride*y); unsigned char *data = row + (rTR.mnSrcX * 4); for (tools::Long x = rTR.mnSrcX; x < rTR.mnSrcX + rTR.mnSrcWidth; ++x) { sal_uInt8 a = data[SVP_CAIRO_ALPHA]; sal_uInt8 b = unpremultiply_table[a][data[SVP_CAIRO_BLUE]]; sal_uInt8 g = unpremultiply_table[a][data[SVP_CAIRO_GREEN]]; sal_uInt8 r = unpremultiply_table[a][data[SVP_CAIRO_RED]]; if (r == 0 && g == 0 && b == 0) { data[0] = nMaskColor.GetBlue(); data[1] = nMaskColor.GetGreen(); data[2] = nMaskColor.GetRed(); data[3] = 0xff; } else { data[0] = 0; data[1] = 0; data[2] = 0; data[3] = 0; } data+=4; } } aSurface.mark_dirty(); cairo_t* cr = m_aCairoCommon.getCairoContext(false, getAntiAlias()); clipRegion(cr); cairo_rectangle(cr, rTR.mnDestX, rTR.mnDestY, rTR.mnDestWidth, rTR.mnDestHeight); basegfx::B2DRange extents = getClippedFillDamage(cr); cairo_clip(cr); cairo_translate(cr, rTR.mnDestX, rTR.mnDestY); double fXScale = static_cast(rTR.mnDestWidth)/rTR.mnSrcWidth; double fYScale = static_cast(rTR.mnDestHeight)/rTR.mnSrcHeight; cairo_scale(cr, fXScale, fYScale); cairo_set_source_surface(cr, aSurface.getSurface(), -rTR.mnSrcX, -rTR.mnSrcY); if ((fXScale != 1.0 && rTR.mnSrcWidth == 1) || (fYScale != 1.0 && rTR.mnSrcHeight == 1)) { cairo_pattern_t* sourcepattern = cairo_get_source(cr); cairo_pattern_set_extend(sourcepattern, CAIRO_EXTEND_REPEAT); cairo_pattern_set_filter(sourcepattern, CAIRO_FILTER_NEAREST); } cairo_paint(cr); m_aCairoCommon.releaseCairoContext(cr, false, extents); } std::shared_ptr SvpSalGraphics::getBitmap( tools::Long nX, tools::Long nY, tools::Long nWidth, tools::Long nHeight ) { std::shared_ptr pBitmap = std::make_shared(); BitmapPalette aPal; vcl::PixelFormat ePixelFormat = vcl::PixelFormat::INVALID; if (GetBitCount() == 1) { ePixelFormat = vcl::PixelFormat::N1_BPP; aPal.SetEntryCount(2); aPal[0] = COL_BLACK; aPal[1] = COL_WHITE; } else { ePixelFormat = vcl::PixelFormat::N32_BPP; } if (!pBitmap->Create(Size(nWidth, nHeight), ePixelFormat, aPal)) { SAL_WARN("vcl.gdi", "SvpSalGraphics::getBitmap, cannot create bitmap"); return nullptr; } cairo_surface_t* target = CairoCommon::createCairoSurface(pBitmap->GetBuffer()); if (!target) { SAL_WARN("vcl.gdi", "SvpSalGraphics::getBitmap, cannot create cairo surface"); return nullptr; } cairo_t* cr = cairo_create(target); SalTwoRect aTR(nX, nY, nWidth, nHeight, 0, 0, nWidth, nHeight); CairoCommon::renderSource(cr, aTR, m_aCairoCommon.m_pSurface); cairo_destroy(cr); cairo_surface_destroy(target); Toggle1BitTransparency(*pBitmap->GetBuffer()); return pBitmap; } #if ENABLE_CAIRO_CANVAS bool SvpSalGraphics::SupportsCairo() const { return false; } cairo::SurfaceSharedPtr SvpSalGraphics::CreateSurface(const cairo::CairoSurfaceSharedPtr& /*rSurface*/) const { return cairo::SurfaceSharedPtr(); } cairo::SurfaceSharedPtr SvpSalGraphics::CreateSurface(const OutputDevice& /*rRefDevice*/, int /*x*/, int /*y*/, int /*width*/, int /*height*/) const { return cairo::SurfaceSharedPtr(); } cairo::SurfaceSharedPtr SvpSalGraphics::CreateBitmapSurface(const OutputDevice& /*rRefDevice*/, const BitmapSystemData& /*rData*/, const Size& /*rSize*/) const { return cairo::SurfaceSharedPtr(); } css::uno::Any SvpSalGraphics::GetNativeSurfaceHandle(cairo::SurfaceSharedPtr& /*rSurface*/, const basegfx::B2ISize& /*rSize*/) const { return css::uno::Any(); } #endif // ENABLE_CAIRO_CANVAS SystemGraphicsData SvpSalGraphics::GetGraphicsData() const { return SystemGraphicsData(); } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */