vcl/Library_vcl.mk | 12 ++ vcl/inc/quartz/cgutils.h | 34 +++++++ vcl/inc/quartz/salbmp.h | 12 -- vcl/inc/salbmp.hxx | 23 +++++ vcl/inc/skia/quartz/cgutils.h | 40 ++++++++ vcl/inc/skia/salbmp.hxx | 7 + vcl/quartz/AquaGraphicsBackend.cxx | 169 ++----------------------------------- vcl/quartz/cgutils.mm | 73 +++++++++++++++ vcl/quartz/salbmp.cxx | 115 ++----------------------- vcl/skia/osx/gdiimpl.cxx | 23 ----- vcl/skia/quartz/salbmp.mm | 107 +++++++++++++++++++++++ 11 files changed, 323 insertions(+), 292 deletions(-)
New commits: commit b99464ef2ff58670aacee2fb5f0ea4398420ad47 Author: Patrick Luby <plub...@neooffice.org> AuthorDate: Sun Dec 25 10:17:01 2022 -0500 Commit: Noel Grandin <noel.gran...@collabora.co.uk> CommitDate: Thu Dec 29 14:14:36 2022 +0000 Related: tdf#146842 Eliminate temporary copies of SkiaSalBitmap when printing Commit 9eb732a32023e74c44ac8c3b5af9f5424273bb6c fixed crashing when printing SkiaSalBitmaps to a non-Skia SalGraphics. However, the fix almost always makes two copies of the SkiaSalBitmap's bitmap data: the first copy is made in SkiaSalBitmap::AcquireBuffer() and then QuartzSalBitmap makes a copy of the first copy. By making QuartzSalBitmap's methods that return a CGImageRef pure virtual, a non-Skia SalGraphics can now create a CGImageRef directly from a SkiaSalBitmap's Skia bitmap data without copying to any intermediate buffers. Change-Id: If6ab7f175889cb4839d8a2461b7be7671e575c08 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/144856 Tested-by: Jenkins Reviewed-by: Noel Grandin <noel.gran...@collabora.co.uk> diff --git a/vcl/Library_vcl.mk b/vcl/Library_vcl.mk index f3a165b830ca..0c018b7bb9b4 100644 --- a/vcl/Library_vcl.mk +++ b/vcl/Library_vcl.mk @@ -684,6 +684,12 @@ ifeq ($(OS),iOS) $(eval $(call gb_Library_add_cxxflags,vcl,\ $(gb_OBJCXXFLAGS) \ )) +$(eval $(call gb_Library_add_objcxxobjects,vcl,\ + vcl/quartz/cgutils \ + $(if $(filter SKIA,$(BUILD_TYPE)), \ + vcl/skia/quartz/salbmp \ + ) \ +)) $(eval $(call gb_Library_add_exception_objects,vcl,\ vcl/ios/iosinst \ vcl/ios/dummies \ @@ -709,6 +715,12 @@ endif ifeq ($(OS),MACOSX) +$(eval $(call gb_Library_add_objcxxobjects,vcl,\ + vcl/quartz/cgutils \ + $(if $(filter SKIA,$(BUILD_TYPE)), \ + vcl/skia/quartz/salbmp \ + ) \ +)) $(eval $(call gb_Library_use_system_darwin_frameworks,vcl,\ Cocoa \ CoreFoundation \ diff --git a/vcl/inc/quartz/cgutils.h b/vcl/inc/quartz/cgutils.h new file mode 100644 index 000000000000..6c499448c721 --- /dev/null +++ b/vcl/inc/quartz/cgutils.h @@ -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 <vcl/dllapi.h> + +#include <premac.h> +#include <CoreGraphics/CoreGraphics.h> +#include <postmac.h> + +class SalBitmap; + +CGImageRef VCL_DLLPUBLIC CreateWithSalBitmapAndMask(const SalBitmap& rBitmap, + const SalBitmap& rMask, int nX, int nY, + int nWidth, int nHeight); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/inc/quartz/salbmp.h b/vcl/inc/quartz/salbmp.h index 459a2b528f99..8f4e6a34ee1c 100644 --- a/vcl/inc/quartz/salbmp.h +++ b/vcl/inc/quartz/salbmp.h @@ -30,9 +30,6 @@ #include <salinst.hxx> #include <salvd.hxx> #include <salbmp.hxx> -#if HAVE_FEATURE_SKIA -#include <skia/salbmp.hxx> -#endif #include <memory> @@ -64,9 +61,6 @@ public: bool Create( const SalBitmap& rSalBmp ) override; bool Create( const SalBitmap& rSalBmp, SalGraphics* pGraphics ) override; bool Create( const SalBitmap& rSalBmp, vcl::PixelFormat eNewPixelFormat) override; -#if HAVE_FEATURE_SKIA - bool Create( const SkiaSalBitmap& rSkiaSalBmp, const SalTwoRect& rPosAry ); -#endif virtual bool Create( const css::uno::Reference< css::rendering::XBitmapCanvas >& rBitmapCanvas, Size& rSize, bool bMask = false ) override; @@ -99,9 +93,9 @@ public: bool Create(CGLayerHolder const & rLayerHolder, int nBitCount, int nX, int nY, int nWidth, int nHeight, bool bFlipped); public: - CGImageRef CreateWithMask( const QuartzSalBitmap& rMask, int nX, int nY, int nWidth, int nHeight ) const; - CGImageRef CreateColorMask( int nX, int nY, int nWidth, int nHeight, Color nMaskColor ) const; - CGImageRef CreateCroppedImage( int nX, int nY, int nWidth, int nHeight ) const; + virtual CGImageRef CreateWithMask( const SalBitmap& rMask, int nX, int nY, int nWidth, int nHeight ) const override; + virtual CGImageRef CreateColorMask( int nX, int nY, int nWidth, int nHeight, Color nMaskColor ) const override; + virtual CGImageRef CreateCroppedImage( int nX, int nY, int nWidth, int nHeight ) const override; void doDestroy(); }; diff --git a/vcl/inc/salbmp.hxx b/vcl/inc/salbmp.hxx index b08d66f522cb..6abaa30ea4a7 100644 --- a/vcl/inc/salbmp.hxx +++ b/vcl/inc/salbmp.hxx @@ -29,6 +29,12 @@ #include <com/sun/star/rendering/XBitmapCanvas.hpp> #include <basegfx/utils/systemdependentdata.hxx> +#if defined MACOSX || defined IOS +#include <premac.h> +#include <CoreGraphics/CoreGraphics.h> +#include <postmac.h> +#endif + struct BitmapBuffer; class Color; class SalGraphics; @@ -98,6 +104,23 @@ public: return false; } +#if defined MACOSX || defined IOS + // Related: tdf#146842 Eliminate temporary copies of SkiaSalBitmap when + // printing + // Commit 9eb732a32023e74c44ac8c3b5af9f5424273bb6c fixed crashing when + // printing SkiaSalBitmaps to a non-Skia SalGraphics. However, the fix + // almost always makes two copies of the SkiaSalBitmap's bitmap data: the + // first copy is made in SkiaSalBitmap::AcquireBuffer() and then + // QuartzSalBitmap makes a copy of the first copy. + // By making QuartzSalBitmap's methods that return a CGImageRef pure + // virtual, a non-Skia SalGraphics can now create a CGImageRef directly + // from a SkiaSalBitmap's Skia bitmap data without copying to any + // intermediate buffers. + virtual CGImageRef CreateWithMask( const SalBitmap& rMask, int nX, int nY, int nWidth, int nHeight ) const = 0; + virtual CGImageRef CreateColorMask( int nX, int nY, int nWidth, int nHeight, Color nMaskColor ) const = 0; + virtual CGImageRef CreateCroppedImage( int nX, int nY, int nWidth, int nHeight ) const = 0; +#endif + BitmapChecksum GetChecksum() const { updateChecksum(); diff --git a/vcl/inc/skia/quartz/cgutils.h b/vcl/inc/skia/quartz/cgutils.h new file mode 100644 index 000000000000..cb43a31dcdff --- /dev/null +++ b/vcl/inc/skia/quartz/cgutils.h @@ -0,0 +1,40 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.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 + +constexpr uint32_t SkiaToCGBitmapType(SkColorType color, SkAlphaType alpha) +{ + if (alpha == kPremul_SkAlphaType) + { + return color == kBGRA_8888_SkColorType + ? (uint32_t(kCGImageAlphaPremultipliedFirst) + | uint32_t(kCGBitmapByteOrder32Little)) + : (uint32_t(kCGImageAlphaPremultipliedLast) | uint32_t(kCGBitmapByteOrder32Big)); + } + else + { + assert(alpha == kOpaque_SkAlphaType); + return color == kBGRA_8888_SkColorType + ? (uint32_t(kCGImageAlphaNoneSkipFirst) | uint32_t(kCGBitmapByteOrder32Little)) + : (uint32_t(kCGImageAlphaNoneSkipLast) | uint32_t(kCGBitmapByteOrder32Big)); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/inc/skia/salbmp.hxx b/vcl/inc/skia/salbmp.hxx index e79fb1cc101d..8a974f08566c 100644 --- a/vcl/inc/skia/salbmp.hxx +++ b/vcl/inc/skia/salbmp.hxx @@ -64,6 +64,13 @@ public: virtual bool ConvertToGreyscale() override; virtual bool Erase(const Color& color) override; virtual bool AlphaBlendWith(const SalBitmap& rSalBmp) override; +#if defined MACOSX || defined IOS + virtual CGImageRef CreateWithMask(const SalBitmap& rMask, int nX, int nY, int nWidth, + int nHeight) const override; + virtual CGImageRef CreateColorMask(int nX, int nY, int nWidth, int nHeight, + Color nMaskColor) const override; + virtual CGImageRef CreateCroppedImage(int nX, int nY, int nWidth, int nHeight) const override; +#endif const BitmapPalette& Palette() const { return mPalette; } diff --git a/vcl/quartz/AquaGraphicsBackend.cxx b/vcl/quartz/AquaGraphicsBackend.cxx index 2dd1acc8d221..700c7dbe4304 100644 --- a/vcl/quartz/AquaGraphicsBackend.cxx +++ b/vcl/quartz/AquaGraphicsBackend.cxx @@ -44,11 +44,6 @@ #include <svdata.hxx> #endif -#if HAVE_FEATURE_SKIA -#include <vcl/skia/SkiaHelper.hxx> -#include <skia/salbmp.hxx> -#endif - using namespace vcl; namespace @@ -192,48 +187,6 @@ void drawPattern50(void*, CGContextRef rContext) CGContextAddRects(rContext, aRects, 2); CGContextFillPath(rContext); } - -#if HAVE_FEATURE_SKIA - -// Related: tdf#146842 Convert SkiaSalBitmap to QuartzSalBitmap -// Commit de3f13e2175564316eb5a62dee65e9ff8f31b460 disabled Skia for printing. -// However, since all SalBitmaps created are either all QuartzSalBitmaps or all -// SkiaSalBitmaps, a crash occurs whenever a SkiaSalBitmap is passed to a -// printer's SalGraphics instance which is now always non-Skia. -QuartzSalBitmap* checkAndConvertToQuartzSalBitmap(const SalTwoRect& rPosAry, - const SalBitmap& rSalBitmap, - SalTwoRect* pAdjustedSrcPosAry) -{ - QuartzSalBitmap* pRet = nullptr; - - if (SkiaHelper::isVCLSkiaEnabled() && dynamic_cast<const SkiaSalBitmap*>(&rSalBitmap)) - { - const SkiaSalBitmap& rSkiaBitmap = static_cast<const SkiaSalBitmap&>(rSalBitmap); - - SalTwoRect aSrcPosAry(rPosAry); - aSrcPosAry.mnDestX = 0; - aSrcPosAry.mnDestY = 0; - - pRet = new QuartzSalBitmap; - if (pRet) - { - // Ignore any failures as returning a nullptr will lead to a crash - pRet->Create(rSkiaBitmap, aSrcPosAry); - - if (pAdjustedSrcPosAry) - { - pAdjustedSrcPosAry->mnSrcX = 0; - pAdjustedSrcPosAry->mnSrcY = 0; - pAdjustedSrcPosAry->mnSrcWidth = aSrcPosAry.mnDestWidth; - pAdjustedSrcPosAry->mnSrcHeight = aSrcPosAry.mnDestHeight; - } - } - } - - return pRet; -} - -#endif } AquaGraphicsBackend::AquaGraphicsBackend(AquaSharedAttributes& rShared) @@ -949,23 +902,7 @@ void AquaGraphicsBackend::drawBitmap(const SalTwoRect& rPosAry, const SalBitmap& if (!mrShared.checkContext()) return; -#if HAVE_FEATURE_SKIA - if (mrShared.mbPrinter) - { - SAL_INFO("vcl.print", "Printing with Skia bitmaps: AquaGraphicsBackend::drawBitmap"); - SalTwoRect aDestPosAry(rPosAry); - std::unique_ptr<QuartzSalBitmap> pSalBitmap( - checkAndConvertToQuartzSalBitmap(rPosAry, rSalBitmap, &aDestPosAry)); - if (pSalBitmap) - { - drawBitmap(aDestPosAry, *pSalBitmap); - return; - } - } -#endif - - const QuartzSalBitmap& rBitmap = static_cast<const QuartzSalBitmap&>(rSalBitmap); - CGImageRef xImage = rBitmap.CreateCroppedImage( + CGImageRef xImage = rSalBitmap.CreateCroppedImage( static_cast<int>(rPosAry.mnSrcX), static_cast<int>(rPosAry.mnSrcY), static_cast<int>(rPosAry.mnSrcWidth), static_cast<int>(rPosAry.mnSrcHeight)); if (!xImage) @@ -985,36 +922,9 @@ void AquaGraphicsBackend::drawBitmap(const SalTwoRect& rPosAry, const SalBitmap& if (!mrShared.checkContext()) return; -#if HAVE_FEATURE_SKIA - if (mrShared.mbPrinter) - { - SAL_INFO( - "vcl.print", - "Printing with Skia bitmaps: AquaGraphicsBackend::drawBitmapWithTranspraentBitmap"); - SalTwoRect aDestPosAry(rPosAry); - std::unique_ptr<QuartzSalBitmap> pSalBitmap( - checkAndConvertToQuartzSalBitmap(rPosAry, rSalBitmap, &aDestPosAry)); - if (pSalBitmap) - { - drawBitmap(aDestPosAry, *pSalBitmap, rTransparentBitmap); - return; - } - - pSalBitmap.reset( - checkAndConvertToQuartzSalBitmap(rPosAry, rTransparentBitmap, &aDestPosAry)); - if (pSalBitmap) - { - drawBitmap(aDestPosAry, rSalBitmap, *pSalBitmap); - return; - } - } -#endif - - const QuartzSalBitmap& rBitmap = static_cast<const QuartzSalBitmap&>(rSalBitmap); - const QuartzSalBitmap& rMask = static_cast<const QuartzSalBitmap&>(rTransparentBitmap); - - CGImageRef xMaskedImage(rBitmap.CreateWithMask(rMask, rPosAry.mnSrcX, rPosAry.mnSrcY, - rPosAry.mnSrcWidth, rPosAry.mnSrcHeight)); + CGImageRef xMaskedImage(rSalBitmap.CreateWithMask(rTransparentBitmap, rPosAry.mnSrcX, + rPosAry.mnSrcY, rPosAry.mnSrcWidth, + rPosAry.mnSrcHeight)); if (!xMaskedImage) return; @@ -1031,24 +941,8 @@ void AquaGraphicsBackend::drawMask(const SalTwoRect& rPosAry, const SalBitmap& r if (!mrShared.checkContext()) return; -#if HAVE_FEATURE_SKIA - if (mrShared.mbPrinter) - { - SAL_INFO("vcl.print", "Printing with Skia bitmaps: AquaGraphicsBackend::drawMask"); - SalTwoRect aDestPosAry(rPosAry); - std::unique_ptr<QuartzSalBitmap> pSalBitmap( - checkAndConvertToQuartzSalBitmap(rPosAry, rSalBitmap, &aDestPosAry)); - if (pSalBitmap) - { - drawMask(aDestPosAry, *pSalBitmap, nMaskColor); - return; - } - } -#endif - - const QuartzSalBitmap& rBitmap = static_cast<const QuartzSalBitmap&>(rSalBitmap); - CGImageRef xImage = rBitmap.CreateColorMask(rPosAry.mnSrcX, rPosAry.mnSrcY, rPosAry.mnSrcWidth, - rPosAry.mnSrcHeight, nMaskColor); + CGImageRef xImage = rSalBitmap.CreateColorMask( + rPosAry.mnSrcX, rPosAry.mnSrcY, rPosAry.mnSrcWidth, rPosAry.mnSrcHeight, nMaskColor); if (!xImage) return; @@ -1324,28 +1218,7 @@ bool AquaGraphicsBackend::drawAlphaBitmap(const SalTwoRect& rTR, const SalBitmap if (rTR.mnDestWidth < 0 || rTR.mnDestHeight < 0) return false; -#if HAVE_FEATURE_SKIA - if (!mrShared.checkContext()) - return false; - - if (mrShared.mbPrinter) - { - SAL_INFO("vcl.print", "Printing with Skia bitmaps: AquaGraphicsBackend::drawAlphaBitmap"); - SalTwoRect aDestPosAry(rTR); - std::unique_ptr<QuartzSalBitmap> pSalBitmap( - checkAndConvertToQuartzSalBitmap(rTR, rSrcBitmap, &aDestPosAry)); - if (pSalBitmap) - return drawAlphaBitmap(aDestPosAry, *pSalBitmap, rAlphaBmp); - - pSalBitmap.reset(checkAndConvertToQuartzSalBitmap(rTR, rAlphaBmp, &aDestPosAry)); - if (pSalBitmap) - return drawAlphaBitmap(aDestPosAry, rSrcBitmap, *pSalBitmap); - } -#endif - - const QuartzSalBitmap& rSrcSalBmp = static_cast<const QuartzSalBitmap&>(rSrcBitmap); - const QuartzSalBitmap& rMaskSalBmp = static_cast<const QuartzSalBitmap&>(rAlphaBmp); - CGImageRef xMaskedImage = rSrcSalBmp.CreateWithMask(rMaskSalBmp, rTR.mnSrcX, rTR.mnSrcY, + CGImageRef xMaskedImage = rSrcBitmap.CreateWithMask(rAlphaBmp, rTR.mnSrcX, rTR.mnSrcY, rTR.mnSrcWidth, rTR.mnSrcHeight); if (!xMaskedImage) return false; @@ -1375,35 +1248,15 @@ bool AquaGraphicsBackend::drawTransformedBitmap(const basegfx::B2DPoint& rNull, if (fAlpha != 1.0) return false; -#if HAVE_FEATURE_SKIA - if (mrShared.mbPrinter) - { - SAL_INFO("vcl.print", - "Printing with Skia bitmaps: AquaGraphicsBackend::drawTransformedBitmap"); - const Size aSize = rSrcBitmap.GetSize(); - SalTwoRect aTR(0, 0, aSize.Width(), aSize.Height(), 0, 0, aSize.Width(), aSize.Height()); - std::unique_ptr<QuartzSalBitmap> pSalBitmap( - checkAndConvertToQuartzSalBitmap(aTR, rSrcBitmap, nullptr)); - if (pSalBitmap) - return drawTransformedBitmap(rNull, rX, rY, *pSalBitmap, pAlphaBmp, fAlpha); - - pSalBitmap.reset(checkAndConvertToQuartzSalBitmap(aTR, *pAlphaBmp, nullptr)); - if (pSalBitmap) - return drawTransformedBitmap(rNull, rX, rY, rSrcBitmap, pSalBitmap.get(), fAlpha); - } -#endif - // get the Quartz image CGImageRef xImage = nullptr; const Size aSize = rSrcBitmap.GetSize(); - const QuartzSalBitmap& rSrcSalBmp = static_cast<const QuartzSalBitmap&>(rSrcBitmap); - const QuartzSalBitmap* pMaskSalBmp = static_cast<const QuartzSalBitmap*>(pAlphaBmp); - if (!pMaskSalBmp) - xImage = rSrcSalBmp.CreateCroppedImage(0, 0, int(aSize.Width()), int(aSize.Height())); + if (!pAlphaBmp) + xImage = rSrcBitmap.CreateCroppedImage(0, 0, int(aSize.Width()), int(aSize.Height())); else - xImage = rSrcSalBmp.CreateWithMask(*pMaskSalBmp, 0, 0, int(aSize.Width()), - int(aSize.Height())); + xImage + = rSrcBitmap.CreateWithMask(*pAlphaBmp, 0, 0, int(aSize.Width()), int(aSize.Height())); if (!xImage) return false; diff --git a/vcl/quartz/cgutils.mm b/vcl/quartz/cgutils.mm new file mode 100644 index 000000000000..61935acc7fdd --- /dev/null +++ b/vcl/quartz/cgutils.mm @@ -0,0 +1,73 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <quartz/cgutils.h> + +#include <salbmp.hxx> +#include <osx/saldata.hxx> + +static void CFRTLFree(void* /*info*/, const void* data, size_t /*size*/) +{ + std::free( const_cast<void*>(data) ); +} + +CGImageRef CreateWithSalBitmapAndMask( const SalBitmap& rBitmap, const SalBitmap& rMask, int nX, int nY, int nWidth, int nHeight ) +{ + CGImageRef xImage( rBitmap.CreateCroppedImage( nX, nY, nWidth, nHeight ) ); + if( !xImage ) + return nullptr; + + CGImageRef xMask = rMask.CreateCroppedImage( nX, nY, nWidth, nHeight ); + if( !xMask ) + return xImage; + + // CGImageCreateWithMask() only likes masks or greyscale images => convert if needed + // TODO: isolate in an extra method? + if( !CGImageIsMask(xMask) || rMask.GetBitCount() != 8)//(CGImageGetColorSpace(xMask) != GetSalData()->mxGraySpace) ) + { + const CGRect xImageRect=CGRectMake( 0, 0, nWidth, nHeight );//the rect has no offset + + // create the alpha mask image fitting our image + // TODO: is caching the full mask or the subimage mask worth it? + int nMaskBytesPerRow = ((nWidth + 3) & ~3); + void* pMaskMem = std::malloc( nMaskBytesPerRow * nHeight ); + CGContextRef xMaskContext = CGBitmapContextCreate( pMaskMem, + nWidth, nHeight, 8, nMaskBytesPerRow, GetSalData()->mxGraySpace, kCGImageAlphaNone ); + CGContextDrawImage( xMaskContext, xImageRect, xMask ); + CFRelease( xMask ); + CGDataProviderRef xDataProvider( CGDataProviderCreateWithData( nullptr, + pMaskMem, nHeight * nMaskBytesPerRow, &CFRTLFree ) ); + + static const CGFloat* pDecode = nullptr; + xMask = CGImageMaskCreate( nWidth, nHeight, 8, 8, nMaskBytesPerRow, xDataProvider, pDecode, false ); + CFRelease( xDataProvider ); + CFRelease( xMaskContext ); + } + + if( !xMask ) + return xImage; + + // combine image and alpha mask + CGImageRef xMaskedImage = CGImageCreateWithMask( xImage, xMask ); + CFRelease( xMask ); + CFRelease( xImage ); + return xMaskedImage; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/quartz/salbmp.cxx b/vcl/quartz/salbmp.cxx index 05b5093bafc1..e71c35612cb4 100644 --- a/vcl/quartz/salbmp.cxx +++ b/vcl/quartz/salbmp.cxx @@ -35,6 +35,7 @@ #include <vcl/Scanline.hxx> #include <bitmap/bmpfast.hxx> +#include <quartz/cgutils.h> #include <quartz/salbmp.h> #include <quartz/utils.h> #include <bitmap/ScanlineTools.hxx> @@ -115,66 +116,6 @@ bool QuartzSalBitmap::Create( const SalBitmap& rSalBmp, vcl::PixelFormat eNewPix return false; } -#if HAVE_FEATURE_SKIA - -bool QuartzSalBitmap::Create( const SkiaSalBitmap& rSalBmp, const SalTwoRect& rPosAry ) -{ - bool bRet = false; - - // Ugly but necessary to acquire the bitmap buffer because all of the - // SalBitmap instances that callers pass are already const. At least we - // only need to read, not write to the bitmap parameter. - SkiaSalBitmap& rSkiaSalBmp = const_cast<SkiaSalBitmap&>( rSalBmp ); - - BitmapBuffer *pSrcBuffer = rSkiaSalBmp.AcquireBuffer( BitmapAccessMode::Read ); - if ( !pSrcBuffer ) - return bRet; - - if ( !pSrcBuffer->mpBits ) - { - rSkiaSalBmp.ReleaseBuffer( pSrcBuffer, BitmapAccessMode::Read ); - return bRet; - } - - // Create only a 1 pixel buffer as it will always be discarded - mnBits = 32; - mnWidth = 1; - mnHeight = 1; - if( AllocateUserData() ) - { - BitmapBuffer *pDestBuffer = AcquireBuffer( BitmapAccessMode::Read ); - if ( pDestBuffer ) - { - std::unique_ptr<BitmapBuffer> pConvertedBuffer = StretchAndConvert( *pSrcBuffer, rPosAry, pDestBuffer->mnFormat, pDestBuffer->maPalette, &pDestBuffer->maColorMask ); - bool bUseDestBuffer = ( pConvertedBuffer && - pConvertedBuffer->mpBits && - pConvertedBuffer->mnFormat == pDestBuffer->mnFormat && - pConvertedBuffer->mnWidth == rPosAry.mnDestWidth && - pConvertedBuffer->mnHeight == rPosAry.mnDestHeight ); - - ReleaseBuffer( pDestBuffer, BitmapAccessMode::Read ); - - if ( bUseDestBuffer ) - { - // Surprisingly, BitmapBuffer does not delete the bits so - // discard our 1 pixel buffer and take ownership of the bits - DestroyContext(); - m_pUserBuffer.reset( pConvertedBuffer->mpBits ); - mnWidth = pConvertedBuffer->mnWidth; - mnHeight = pConvertedBuffer->mnHeight; - mnBytesPerRow = pConvertedBuffer->mnScanlineSize; - bRet = true; - } - } - } - - rSkiaSalBmp.ReleaseBuffer( pSrcBuffer, BitmapAccessMode::Read ); - - return bRet; -} - -#endif - bool QuartzSalBitmap::Create( const css::uno::Reference< css::rendering::XBitmapCanvas >& /*xBitmapCanvas*/, Size& /*rSize*/, bool /*bMask*/ ) { @@ -576,48 +517,10 @@ static void CFRTLFree(void* /*info*/, const void* data, size_t /*size*/) std::free( const_cast<void*>(data) ); } -CGImageRef QuartzSalBitmap::CreateWithMask( const QuartzSalBitmap& rMask, +CGImageRef QuartzSalBitmap::CreateWithMask( const SalBitmap& rMask, int nX, int nY, int nWidth, int nHeight ) const { - CGImageRef xImage( CreateCroppedImage( nX, nY, nWidth, nHeight ) ); - if( !xImage ) - return nullptr; - - CGImageRef xMask = rMask.CreateCroppedImage( nX, nY, nWidth, nHeight ); - if( !xMask ) - return xImage; - - // CGImageCreateWithMask() only likes masks or greyscale images => convert if needed - // TODO: isolate in an extra method? - if( !CGImageIsMask(xMask) || rMask.GetBitCount() != 8)//(CGImageGetColorSpace(xMask) != GetSalData()->mxGraySpace) ) - { - const CGRect xImageRect=CGRectMake( 0, 0, nWidth, nHeight );//the rect has no offset - - // create the alpha mask image fitting our image - // TODO: is caching the full mask or the subimage mask worth it? - int nMaskBytesPerRow = ((nWidth + 3) & ~3); - void* pMaskMem = std::malloc( nMaskBytesPerRow * nHeight ); - CGContextRef xMaskContext = CGBitmapContextCreate( pMaskMem, - nWidth, nHeight, 8, nMaskBytesPerRow, GetSalData()->mxGraySpace, kCGImageAlphaNone ); - CGContextDrawImage( xMaskContext, xImageRect, xMask ); - CFRelease( xMask ); - CGDataProviderRef xDataProvider( CGDataProviderCreateWithData( nullptr, - pMaskMem, nHeight * nMaskBytesPerRow, &CFRTLFree ) ); - - static const CGFloat* pDecode = nullptr; - xMask = CGImageMaskCreate( nWidth, nHeight, 8, 8, nMaskBytesPerRow, xDataProvider, pDecode, false ); - CFRelease( xDataProvider ); - CFRelease( xMaskContext ); - } - - if( !xMask ) - return xImage; - - // combine image and alpha mask - CGImageRef xMaskedImage = CGImageCreateWithMask( xImage, xMask ); - CFRelease( xMask ); - CFRelease( xImage ); - return xMaskedImage; + return CreateWithSalBitmapAndMask( *this, rMask, nX, nY, nWidth, nHeight ); } /** creates an image from the given rectangle, replacing all black pixels @@ -628,13 +531,14 @@ CGImageRef QuartzSalBitmap::CreateColorMask( int nX, int nY, int nWidth, CGImageRef xMask = nullptr; if (m_pUserBuffer && (nX + nWidth <= mnWidth) && (nY + nHeight <= mnHeight)) { - const sal_uInt32 nDestBytesPerRow = nWidth << 2; - std::unique_ptr<sal_uInt32[]> pMaskBuffer(new (std::nothrow) sal_uInt32[ nHeight * nDestBytesPerRow / 4] ); - sal_uInt32* pDest = pMaskBuffer.get(); - auto pSourcePixels = vcl::bitmap::getScanlineTransformer(mnBits, maPalette); + // Don't allocate destination buffer if there is no scanline transformer + if( !pSourcePixels ) + return xMask; - if( pMaskBuffer && pSourcePixels ) + const sal_uInt32 nDestBytesPerRow = nWidth << 2; + std::unique_ptr<sal_uInt32[]> pMaskBuffer(new (std::nothrow) sal_uInt32[ nHeight * nDestBytesPerRow / 4] ); + if( pMaskBuffer ) { sal_uInt32 nColor; reinterpret_cast<sal_uInt8*>(&nColor)[0] = 0xff; @@ -643,6 +547,7 @@ CGImageRef QuartzSalBitmap::CreateColorMask( int nX, int nY, int nWidth, reinterpret_cast<sal_uInt8*>(&nColor)[3] = nMaskColor.GetBlue(); sal_uInt8* pSource = m_pUserBuffer.get(); + sal_uInt32* pDest = pMaskBuffer.get(); // First to nY on y-axis, as that is our starting point (sub-image) if( nY ) pSource += nY * mnBytesPerRow; diff --git a/vcl/skia/osx/gdiimpl.cxx b/vcl/skia/osx/gdiimpl.cxx index 9d3765c3cc03..1c1b7d2955c3 100644 --- a/vcl/skia/osx/gdiimpl.cxx +++ b/vcl/skia/osx/gdiimpl.cxx @@ -26,6 +26,7 @@ #include <tools/sk_app/mac/WindowContextFactory_mac.h> #include <quartz/ctfonts.hxx> +#include <skia/quartz/cgutils.h> #include <SkBitmap.h> #include <SkCanvas.h> @@ -102,24 +103,6 @@ void AquaSkiaSalGraphicsImpl::flushSurfaceToWindowContext() SkiaSalGraphicsImpl::flushSurfaceToWindowContext(); } -constexpr static uint32_t toCGBitmapType(SkColorType color, SkAlphaType alpha) -{ - if (alpha == kPremul_SkAlphaType) - { - return color == kBGRA_8888_SkColorType - ? (uint32_t(kCGImageAlphaPremultipliedFirst) - | uint32_t(kCGBitmapByteOrder32Little)) - : (uint32_t(kCGImageAlphaPremultipliedLast) | uint32_t(kCGBitmapByteOrder32Big)); - } - else - { - assert(alpha == kOpaque_SkAlphaType); - return color == kBGRA_8888_SkColorType - ? (uint32_t(kCGImageAlphaNoneSkipFirst) | uint32_t(kCGBitmapByteOrder32Little)) - : (uint32_t(kCGImageAlphaNoneSkipLast) | uint32_t(kCGBitmapByteOrder32Big)); - } -} - // For Raster we use our own screen blitting (see above). void AquaSkiaSalGraphicsImpl::flushSurfaceToScreenCG() { @@ -174,7 +157,7 @@ void AquaSkiaSalGraphicsImpl::flushSurfaceToScreenCG() CGImageRef fullImage = CGImageCreate(pixmap.bounds().width(), pixmap.bounds().height(), 8, 8 * image->imageInfo().bytesPerPixel(), pixmap.rowBytes(), GetSalData()->mxRGBSpace, - toCGBitmapType(image->colorType(), image->alphaType()), + SkiaToCGBitmapType(image->colorType(), image->alphaType()), dataProvider, nullptr, false, kCGRenderingIntentDefault); if (!fullImage) { @@ -247,7 +230,7 @@ bool AquaSkiaSalGraphicsImpl::drawNativeControl(ControlType nType, ControlPart n memset(data.get(), 0, bytes); CGContextRef context = CGBitmapContextCreate( data.get(), width, height, 8, width * 4, GetSalData()->mxRGBSpace, - toCGBitmapType(mSurface->imageInfo().colorType(), kPremul_SkAlphaType)); + SkiaToCGBitmapType(mSurface->imageInfo().colorType(), kPremul_SkAlphaType)); if (!context) { SAL_WARN("vcl.skia", "drawNativeControl(): Failed to allocate bitmap context"); diff --git a/vcl/skia/quartz/salbmp.mm b/vcl/skia/quartz/salbmp.mm new file mode 100644 index 000000000000..4e9782daf1d4 --- /dev/null +++ b/vcl/skia/quartz/salbmp.mm @@ -0,0 +1,107 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <skia/salbmp.hxx> +#include <skia/zone.hxx> +#include <SkBitmap.h> +#include <SkCanvas.h> +#include <SkPaint.h> + +#include <quartz/cgutils.h> +#ifdef MACOSX +#include <osx/saldata.hxx> +#else +#include <svdata.hxx> +#endif +#include <skia/quartz/cgutils.h> + +CGImageRef SkiaSalBitmap::CreateWithMask(const SalBitmap& rMask, int nX, int nY, int nWidth, + int nHeight) const +{ + return CreateWithSalBitmapAndMask( *this, rMask, nX, nY, nWidth, nHeight ); +} + +/** creates an image from the given rectangle, replacing all black pixels + with nMaskColor and make all other full transparent */ +CGImageRef SkiaSalBitmap::CreateColorMask(int nX, int nY, int nWidth, int nHeight, + Color nMaskColor) const +{ + SkiaZone zone; + SkBitmap targetBitmap; + if (!targetBitmap.tryAllocPixels(SkImageInfo::Make(nWidth, nHeight, kN32_SkColorType, kPremul_SkAlphaType))) + return nullptr; + + SkPaint paint; + paint.setBlendMode(SkBlendMode::kSrc); // set as is + SkMatrix matrix; // The image is needed upside-down. + matrix.preTranslate(0, targetBitmap.height()); + matrix.setConcat(matrix, SkMatrix::Scale(1, -1)); + + SkCanvas canvas(targetBitmap); + canvas.concat(matrix); + canvas.drawImageRect(GetSkImage(), SkRect::MakeXYWH(nX, nY, nWidth, nHeight), SkRect::MakeXYWH(0, 0, nWidth, nHeight), SkSamplingOptions(), &paint, SkCanvas::SrcRectConstraint::kFast_SrcRectConstraint); + canvas.flush(); + + const SkColor maskColor = SkiaHelper::toSkColor(nMaskColor); + for (int y = 0; y < targetBitmap.height(); y++) + { + SkColor *pPixels = targetBitmap.getAddr32(0, y); + for (int x = 0; x < targetBitmap.width(); x++) + *pPixels++ = (*pPixels == SK_ColorBLACK ? maskColor : SK_ColorTRANSPARENT); + } + + targetBitmap.setImmutable(); + + CGContextRef xContext = CGBitmapContextCreate(targetBitmap.getAddr32(0, 0), targetBitmap.width(), targetBitmap.height(), 8, targetBitmap.rowBytes(), GetSalData()->mxRGBSpace, SkiaToCGBitmapType(targetBitmap.colorType(), targetBitmap.alphaType())); + if (!xContext) + return nullptr; + + CGImageRef xCroppedImage = CGBitmapContextCreateImage(xContext); + CGContextRelease(xContext); + return xCroppedImage; +} + + +CGImageRef SkiaSalBitmap::CreateCroppedImage(int nX, int nY, int nWidth, int nHeight) const +{ + SkiaZone zone; + SkBitmap targetBitmap; + if (!targetBitmap.tryAllocPixels(SkImageInfo::Make(nWidth, nHeight, kN32_SkColorType, kPremul_SkAlphaType))) + return nullptr; + + SkPaint paint; + paint.setBlendMode(SkBlendMode::kSrc); // set as is + SkMatrix matrix; // The image is needed upside-down. + matrix.preTranslate(0, targetBitmap.height()); + matrix.setConcat(matrix, SkMatrix::Scale(1, -1)); + + SkCanvas canvas(targetBitmap); + canvas.concat(matrix); + canvas.drawImageRect(GetSkImage(), SkRect::MakeXYWH(nX, nY, nWidth, nHeight), SkRect::MakeXYWH(0, 0, nWidth, nHeight), SkSamplingOptions(), &paint, SkCanvas::SrcRectConstraint::kFast_SrcRectConstraint); + canvas.flush(); + targetBitmap.setImmutable(); + + CGContextRef xContext = CGBitmapContextCreate(targetBitmap.getAddr32(0, 0), targetBitmap.width(), targetBitmap.height(), 8, targetBitmap.rowBytes(), GetSalData()->mxRGBSpace, SkiaToCGBitmapType(targetBitmap.colorType(), targetBitmap.alphaType())); + if (!xContext) + return nullptr; + + CGImageRef xCroppedImage = CGBitmapContextCreateImage(xContext); + CGContextRelease(xContext); + return xCroppedImage; +}