| /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
| /* |
| * This file is part of the Collabora Office project. |
| * |
| * This Source Code Form is subject to the terms of the Mozilla Public |
| * License, v. 2.0. If a copy of the MPL was not distributed with this |
| * file, You can obtain one at http://mozilla.org/MPL/2.0/. |
| * |
| */ |
| |
| #include <sal/config.h> |
| |
| #include <array> |
| #include <utility> |
| |
| #include <tools/helpers.hxx> |
| #include <vcl/BitmapTools.hxx> |
| |
| #include <sal/log.hxx> |
| #include <comphelper/processfactory.hxx> |
| #include <comphelper/seqstream.hxx> |
| #include <vcl/canvastools.hxx> |
| #include <basegfx/matrix/b2dhommatrix.hxx> |
| |
| #include <com/sun/star/graphic/SvgTools.hpp> |
| #include <com/sun/star/graphic/Primitive2DTools.hpp> |
| |
| #include <drawinglayer/primitive2d/baseprimitive2d.hxx> |
| |
| #include <com/sun/star/rendering/XIntegerReadOnlyBitmap.hpp> |
| |
| #include <vcl/dibtools.hxx> |
| #include <vcl/settings.hxx> |
| #include <vcl/svapp.hxx> |
| #include <vcl/virdev.hxx> |
| #if ENABLE_CAIRO_CANVAS |
| #include <cairo.h> |
| #endif |
| #include <comphelper/diagnose_ex.hxx> |
| #include <tools/fract.hxx> |
| #include <tools/stream.hxx> |
| #include <vcl/BitmapWriteAccess.hxx> |
| |
| using namespace css; |
| |
| using drawinglayer::primitive2d::Primitive2DSequence; |
| using drawinglayer::primitive2d::Primitive2DReference; |
| |
| namespace vcl::bitmap |
| { |
| |
| Bitmap loadFromName(const OUString& rFileName, const ImageLoadFlags eFlags) |
| { |
| bool bSuccess = true; |
| OUString aIconTheme; |
| Bitmap aBitmap; |
| try |
| { |
| aIconTheme = Application::GetSettings().GetStyleSettings().DetermineIconTheme(); |
| ImageTree::get().loadImage(rFileName, aIconTheme, aBitmap, true, eFlags); |
| } |
| catch (...) |
| { |
| bSuccess = false; |
| } |
| |
| SAL_WARN_IF(!bSuccess, "vcl", "vcl::bitmap::loadFromName : could not load image " << rFileName << " via icon theme " << aIconTheme); |
| |
| return aBitmap; |
| } |
| |
| void loadFromSvg(SvStream& rStream, const OUString& sPath, Bitmap& rBitmap, double fScalingFactor) |
| { |
| const uno::Reference<uno::XComponentContext>& xContext(comphelper::getProcessComponentContext()); |
| const uno::Reference<graphic::XSvgParser> xSvgParser = graphic::SvgTools::create(xContext); |
| |
| std::size_t nSize = rStream.remainingSize(); |
| std::vector<sal_Int8> aBuffer(nSize + 1); |
| rStream.ReadBytes(aBuffer.data(), nSize); |
| aBuffer[nSize] = 0; |
| |
| uno::Sequence<sal_Int8> aData(aBuffer.data(), nSize + 1); |
| uno::Reference<io::XInputStream> aInputStream(new comphelper::SequenceInputStream(aData)); |
| |
| const Primitive2DSequence aPrimitiveSequence = xSvgParser->getDecomposition(aInputStream, sPath); |
| |
| if (!aPrimitiveSequence.hasElements()) |
| return; |
| |
| uno::Sequence<beans::PropertyValue> aViewParameters; |
| |
| geometry::RealRectangle2D aRealRect; |
| basegfx::B2DRange aRange; |
| for (css::uno::Reference<css::graphic::XPrimitive2D> const & xReference : aPrimitiveSequence) |
| { |
| if (xReference.is()) |
| { |
| aRealRect = xReference->getRange(aViewParameters); |
| aRange.expand(basegfx::B2DRange(aRealRect.X1, aRealRect.Y1, aRealRect.X2, aRealRect.Y2)); |
| } |
| } |
| |
| aRealRect.X1 = aRange.getMinX(); |
| aRealRect.Y1 = aRange.getMinY(); |
| aRealRect.X2 = aRange.getMaxX(); |
| aRealRect.Y2 = aRange.getMaxY(); |
| |
| double nDPI = 96 * fScalingFactor; |
| |
| const css::uno::Reference<css::graphic::XPrimitive2DRenderer> xPrimitive2DRenderer = css::graphic::Primitive2DTools::create(xContext); |
| const css::uno::Reference<css::rendering::XBitmap> xBitmap( |
| xPrimitive2DRenderer->rasterize(aPrimitiveSequence, aViewParameters, nDPI, nDPI, aRealRect, 256*256)); |
| |
| if (xBitmap.is()) |
| { |
| const css::uno::Reference<css::rendering::XIntegerReadOnlyBitmap> xIntBmp(xBitmap, uno::UNO_QUERY_THROW); |
| rBitmap = vcl::unotools::bitmapFromXBitmap(xIntBmp); |
| } |
| |
| } |
| |
| /** Copy block of image data into the bitmap. |
| Assumes that the Bitmap has been constructed with the desired size. |
| |
| @param pData |
| The block of data to copy |
| @param nStride |
| The number of bytes in a scanline, must >= (width * nBitCount / 8) |
| @param bReversColors |
| In case the endianness of pData is wrong, you could reverse colors |
| */ |
| Bitmap CreateFromData(sal_uInt8 const *pData, sal_Int32 nWidth, sal_Int32 nHeight, |
| sal_Int32 nStride, sal_Int8 nBitCount, |
| bool bReversColors, bool bReverseAlpha) |
| { |
| assert(nStride >= (nWidth * nBitCount / 8)); |
| assert(nBitCount == 1 || nBitCount == 8 || nBitCount == 24 || nBitCount == 32); |
| |
| PixelFormat ePixelFormat; |
| if (nBitCount == 1) |
| ePixelFormat = PixelFormat::N8_BPP; // we convert 1-bit input data to 8-bit format |
| else if (nBitCount == 8) |
| ePixelFormat = PixelFormat::N8_BPP; |
| else if (nBitCount == 24) |
| ePixelFormat = PixelFormat::N24_BPP; |
| else if (nBitCount == 32) |
| ePixelFormat = PixelFormat::N32_BPP; |
| else |
| std::abort(); |
| Bitmap aBmp; |
| if (nBitCount == 1) |
| { |
| BitmapPalette aBiLevelPalette { COL_BLACK, COL_WHITE }; |
| aBmp = Bitmap(Size(nWidth, nHeight), PixelFormat::N8_BPP, &aBiLevelPalette); |
| } |
| else |
| aBmp = Bitmap(Size(nWidth, nHeight), ePixelFormat); |
| |
| BitmapScopedWriteAccess pWrite(aBmp); |
| assert(pWrite.get()); |
| if( !pWrite ) |
| return Bitmap(); |
| if (nBitCount == 32) |
| { |
| for( tools::Long y = 0; y < nHeight; ++y ) |
| { |
| sal_uInt8 const *p = pData + (y * nStride); |
| Scanline pScanline = pWrite->GetScanline(y); |
| for (tools::Long x = 0; x < nWidth; ++x) |
| { |
| // FIXME this parameter is badly named |
| const sal_uInt8 nAlphaValue = bReverseAlpha ? p[3] : 0xff - p[3]; |
| BitmapColor col; |
| if ( bReversColors ) |
| col = BitmapColor( ColorAlpha, p[2], p[1], p[0], nAlphaValue ); |
| else |
| col = BitmapColor( ColorAlpha, p[0], p[1], p[2], nAlphaValue ); |
| pWrite->SetPixelOnData(pScanline, x, col); |
| p += 4; |
| } |
| } |
| } |
| else if (nBitCount == 1) |
| { |
| for( tools::Long y = 0; y < nHeight; ++y ) |
| { |
| sal_uInt8 const *p = pData + y * nStride / 8; |
| Scanline pScanline = pWrite->GetScanline(y); |
| for (tools::Long x = 0; x < nWidth; ++x) |
| { |
| int bitIndex = (y * nStride + x) % 8; |
| |
| pWrite->SetPixelOnData(pScanline, x, BitmapColor((*p >> bitIndex) & 1)); |
| } |
| } |
| } |
| else |
| { |
| for( tools::Long y = 0; y < nHeight; ++y ) |
| { |
| sal_uInt8 const *p = pData + (y * nStride); |
| Scanline pScanline = pWrite->GetScanline(y); |
| for (tools::Long x = 0; x < nWidth; ++x) |
| { |
| BitmapColor col; |
| if (nBitCount == 8) |
| col = BitmapColor( *p ); |
| else if ( bReversColors ) |
| col = BitmapColor( p[2], p[1], p[0] ); |
| else |
| col = BitmapColor( p[0], p[1], p[2] ); |
| pWrite->SetPixelOnData(pScanline, x, col); |
| p += nBitCount/8; |
| } |
| } |
| } |
| // Avoid further bitmap use with unfinished write access |
| pWrite.reset(); |
| return aBmp; |
| } |
| |
| /** Copy block of image data into the bitmap. |
| Assumes that the Bitmap has been constructed with the desired size. |
| */ |
| Bitmap CreateFromData( RawBitmap&& rawBitmap ) |
| { |
| auto nBitCount = rawBitmap.GetBitCount(); |
| assert( nBitCount == 24 || nBitCount == 32); |
| |
| auto ePixelFormat = vcl::PixelFormat::INVALID; |
| |
| if (nBitCount == 24) |
| ePixelFormat = vcl::PixelFormat::N24_BPP; |
| else if (nBitCount == 32) |
| ePixelFormat = vcl::PixelFormat::N32_BPP; |
| |
| assert(ePixelFormat != vcl::PixelFormat::INVALID); |
| |
| Bitmap aBmp(rawBitmap.maSize, ePixelFormat); |
| |
| BitmapScopedWriteAccess pWrite(aBmp); |
| assert(pWrite.get()); |
| if( !pWrite ) |
| return Bitmap(); |
| |
| auto nHeight = rawBitmap.maSize.getHeight(); |
| auto nWidth = rawBitmap.maSize.getWidth(); |
| auto nStride = nWidth * nBitCount / 8; |
| if (nBitCount == 32) |
| { |
| for( tools::Long y = 0; y < nHeight; ++y ) |
| { |
| sal_uInt8 const *p = rawBitmap.mpData.get() + (y * nStride); |
| Scanline pScanline = pWrite->GetScanline(y); |
| for (tools::Long x = 0; x < nWidth; ++x) |
| { |
| BitmapColor col(ColorAlpha, p[0], p[1], p[2], p[3]); |
| pWrite->SetPixelOnData(pScanline, x, col); |
| p += 4; |
| } |
| } |
| } |
| else |
| { |
| for( tools::Long y = 0; y < nHeight; ++y ) |
| { |
| sal_uInt8 const *p = rawBitmap.mpData.get() + (y * nStride); |
| Scanline pScanline = pWrite->GetScanline(y); |
| for (tools::Long x = 0; x < nWidth; ++x) |
| { |
| BitmapColor col(p[0], p[1], p[2]); |
| pWrite->SetPixelOnData(pScanline, x, col); |
| p += nBitCount/8; |
| } |
| } |
| } |
| pWrite.reset(); |
| |
| return aBmp; |
| } |
| |
| void fillWithData(sal_uInt8* pData, Bitmap const& rBitmap) |
| { |
| BitmapScopedReadAccess aReadAccess(rBitmap); |
| assert(aReadAccess); |
| |
| sal_uInt8* p = pData; |
| |
| for (tools::Long y = 0, nHeight = aReadAccess->Height(); y < nHeight; ++y) |
| { |
| Scanline pScanline = aReadAccess->GetScanline(y); |
| |
| for (tools::Long x = 0, nWidth = aReadAccess->Width(); x < nWidth; ++x) |
| { |
| BitmapColor aColor = aReadAccess->GetPixelFromData(pScanline, x); |
| *p++ = aColor.GetBlue(); |
| *p++ = aColor.GetGreen(); |
| *p++ = aColor.GetRed(); |
| *p++ = aColor.GetAlpha(); |
| } |
| } |
| } |
| |
| |
| #if ENABLE_CAIRO_CANVAS |
| Bitmap CreateFromCairoSurface(Size aSize, cairo_surface_t * pSurface) |
| { |
| // FIXME: if we could teach VCL/ about cairo handles, life could |
| // be significantly better here perhaps. |
| |
| cairo_surface_t *pPixels = cairo_surface_create_similar_image(pSurface, |
| CAIRO_FORMAT_ARGB32, aSize.Width(), aSize.Height()); |
| cairo_t *pCairo = cairo_create( pPixels ); |
| if( !pPixels || !pCairo || cairo_status(pCairo) != CAIRO_STATUS_SUCCESS ) |
| return Bitmap(); |
| |
| // suck ourselves from the X server to this buffer so then we can fiddle with |
| // Alpha to turn it into the ultra-lame vcl required format and then push it |
| // all back again later at vast expense [ urgh ] |
| cairo_set_source_surface( pCairo, pSurface, 0, 0 ); |
| cairo_set_operator( pCairo, CAIRO_OPERATOR_SOURCE ); |
| cairo_paint( pCairo ); |
| |
| Bitmap aRGBA(aSize, vcl::PixelFormat::N32_BPP); |
| |
| BitmapScopedWriteAccess pRGBAWrite(aRGBA); |
| assert(pRGBAWrite); |
| if (!pRGBAWrite) |
| return Bitmap(); |
| |
| cairo_surface_flush(pPixels); |
| unsigned char *pSrc = cairo_image_surface_get_data( pPixels ); |
| unsigned int nStride = cairo_image_surface_get_stride( pPixels ); |
| #if !ENABLE_WASM_STRIP_PREMULTIPLY |
| vcl::bitmap::lookup_table const & unpremultiply_table = vcl::bitmap::get_unpremultiply_table(); |
| #endif |
| for( tools::Long y = 0; y < aSize.Height(); y++ ) |
| { |
| sal_uInt32 *pPix = reinterpret_cast<sal_uInt32 *>(pSrc + nStride * y); |
| for( tools::Long x = 0; x < aSize.Width(); x++ ) |
| { |
| #if defined OSL_BIGENDIAN |
| sal_uInt8 nB = (*pPix >> 24); |
| sal_uInt8 nG = (*pPix >> 16) & 0xff; |
| sal_uInt8 nR = (*pPix >> 8) & 0xff; |
| sal_uInt8 nAlpha = *pPix & 0xff; |
| #else |
| sal_uInt8 nAlpha = (*pPix >> 24); |
| sal_uInt8 nR = (*pPix >> 16) & 0xff; |
| sal_uInt8 nG = (*pPix >> 8) & 0xff; |
| sal_uInt8 nB = *pPix & 0xff; |
| #endif |
| if( nAlpha != 0 && nAlpha != 255 ) |
| { |
| // Cairo uses pre-multiplied alpha - we do not => re-multiply |
| #if ENABLE_WASM_STRIP_PREMULTIPLY |
| nR = vcl::bitmap::unpremultiply(nR, nAlpha); |
| nG = vcl::bitmap::unpremultiply(nG, nAlpha); |
| nB = vcl::bitmap::unpremultiply(nB, nAlpha); |
| #else |
| nR = unpremultiply_table[nAlpha][nR]; |
| nG = unpremultiply_table[nAlpha][nG]; |
| nB = unpremultiply_table[nAlpha][nB]; |
| #endif |
| } |
| pRGBAWrite->SetPixel( y, x, BitmapColor( ColorAlpha, nR, nG, nB, nAlpha ) ); |
| pPix++; |
| } |
| } |
| |
| pRGBAWrite.reset(); |
| |
| // ignore potential errors above. will get caller a |
| // uniformly white bitmap, but not that there would |
| // be error handling in calling code ... |
| |
| cairo_destroy( pCairo ); |
| cairo_surface_destroy( pPixels ); |
| return aRGBA; |
| } |
| #endif |
| |
| Bitmap CanvasTransformBitmap( const Bitmap& rSrcBitmap, |
| const ::basegfx::B2DHomMatrix& rTransform, |
| ::basegfx::B2DRectangle const & rDestRect, |
| ::basegfx::B2DHomMatrix const & rLocalTransform ) |
| { |
| const Size aDestBmpSize( ::basegfx::fround<tools::Long>( rDestRect.getWidth() ), |
| ::basegfx::fround<tools::Long>( rDestRect.getHeight() ) ); |
| |
| if( aDestBmpSize.IsEmpty() ) |
| return Bitmap(); |
| |
| const Size aBmpSize( rSrcBitmap.GetSizePixel() ); |
| |
| // differentiate mask and alpha channel (on-off |
| // vs. multi-level transparency) |
| |
| BitmapScopedReadAccess pReadAccess( rSrcBitmap ); |
| |
| if( !pReadAccess ) |
| { |
| // TODO(E2): Error handling! |
| ENSURE_OR_THROW( false, |
| "transformBitmap(): could not access source bitmap" ); |
| } |
| |
| // mapping table, to translate pAlphaReadAccess' pixel |
| // values into destination alpha values (needed e.g. for |
| // paletted 1-bit masks). |
| sal_uInt8 aAlphaMap[256]; |
| |
| if( rSrcBitmap.HasAlpha() ) |
| { |
| // source already has alpha channel - 1:1 mapping, |
| // i.e. aAlphaMap[0]=0,...,aAlphaMap[255]=255. |
| sal_uInt8 val=0; |
| sal_uInt8* pCur=aAlphaMap; |
| sal_uInt8* const pEnd=&aAlphaMap[256]; |
| while(pCur != pEnd) |
| *pCur++ = val++; |
| } |
| // else: mapping table is not used |
| |
| Bitmap aDstBitmap(aDestBmpSize, rSrcBitmap.getPixelFormat(), &pReadAccess->GetPalette()); |
| |
| { |
| // just to be on the safe side: let the |
| // ScopedAccessors get destructed before |
| // copy-constructing the resulting bitmap. This will |
| // rule out the possibility that cached accessor data |
| // is not yet written back. |
| BitmapScopedWriteAccess pWriteAccess( aDstBitmap ); |
| |
| if( pWriteAccess.get() != nullptr && |
| rTransform.isInvertible() ) |
| { |
| // we're doing inverse mapping here, i.e. mapping |
| // points from the destination bitmap back to the |
| // source |
| ::basegfx::B2DHomMatrix aTransform( rLocalTransform ); |
| aTransform.invert(); |
| |
| // for the time being, always read as ARGB |
| for( tools::Long y=0; y<aDestBmpSize.Height(); ++y ) |
| { |
| // differentiate mask and alpha channel (on-off |
| // vs. multi-level transparency) |
| if( rSrcBitmap.HasAlpha() ) |
| { |
| Scanline pScan = pWriteAccess->GetScanline( y ); |
| // Handling alpha and mask just the same... |
| for( tools::Long x=0; x<aDestBmpSize.Width(); ++x ) |
| { |
| ::basegfx::B2DPoint aPoint(x,y); |
| aPoint *= aTransform; |
| |
| const tools::Long nSrcX( ::basegfx::fround<tools::Long>( aPoint.getX() ) ); |
| const tools::Long nSrcY( ::basegfx::fround<tools::Long>( aPoint.getY() ) ); |
| if( nSrcX < 0 || nSrcX >= aBmpSize.Width() || |
| nSrcY < 0 || nSrcY >= aBmpSize.Height() ) |
| { |
| pWriteAccess->SetPixelOnData( pScan, x, BitmapColor(ColorAlpha, 0, 0, 0, 0) ); |
| } |
| else |
| { |
| BitmapColor aCol = pReadAccess->GetPixel( nSrcY, nSrcX ); |
| const sal_uInt8 cAlphaIdx = aCol.GetAlpha(); |
| aCol.SetAlpha(aAlphaMap[ cAlphaIdx ]); |
| pWriteAccess->SetPixelOnData( pScan, x, aCol ); |
| } |
| } |
| } |
| else |
| { |
| Scanline pScan = pWriteAccess->GetScanline( y ); |
| for( tools::Long x=0; x<aDestBmpSize.Width(); ++x ) |
| { |
| ::basegfx::B2DPoint aPoint(x,y); |
| aPoint *= aTransform; |
| |
| const tools::Long nSrcX( ::basegfx::fround<tools::Long>( aPoint.getX() ) ); |
| const tools::Long nSrcY( ::basegfx::fround<tools::Long>( aPoint.getY() ) ); |
| if( nSrcX < 0 || nSrcX >= aBmpSize.Width() || |
| nSrcY < 0 || nSrcY >= aBmpSize.Height() ) |
| { |
| pWriteAccess->SetPixelOnData( pScan, x, BitmapColor(ColorAlpha, 0, 0, 0, 0) ); |
| } |
| else |
| { |
| BitmapColor aCol = pReadAccess->GetPixel( nSrcY, nSrcX ); |
| aCol.SetAlpha(255); |
| pWriteAccess->SetPixelOnData( pScan, x, aCol ); |
| } |
| } |
| } |
| } |
| } |
| else |
| { |
| // TODO(E2): Error handling! |
| ENSURE_OR_THROW( false, |
| "transformBitmap(): could not access bitmap" ); |
| } |
| } |
| |
| return aDstBitmap; |
| } |
| |
| // mix existing and new alpha mask |
| void DrawAlphaBitmapAndAlphaGradient(Bitmap & rBitmap, bool bFixedTransparence, float fTransparence, const AlphaMask & rNewMask) |
| { |
| const double fFactor(1.0 / 255.0); |
| BitmapScopedWriteAccess pOld(rBitmap); |
| assert(pOld && "Got no access to old alpha mask (!)"); |
| |
| if(bFixedTransparence) |
| { |
| const double fOpNew(1.0 - fTransparence); |
| |
| for(tools::Long y(0),nHeight(pOld->Height()); y < nHeight; y++) |
| { |
| Scanline pScanline = pOld->GetScanline( y ); |
| for(tools::Long x(0),nWidth(pOld->Width()); x < nWidth; x++) |
| { |
| BitmapColor aCol = pOld->GetPixelFromData(pScanline, x); |
| const double fOpOld(aCol.GetAlpha() * fFactor); |
| aCol.SetAlpha(basegfx::fround((fOpOld * fOpNew) * 255.0)); |
| |
| pOld->SetPixelOnData(pScanline, x, aCol); |
| } |
| } |
| } |
| else |
| { |
| BitmapScopedReadAccess pNew(rNewMask); |
| assert(pNew && "Got no access to new alpha mask (!)"); |
| |
| assert(pOld->Width() == pNew->Width() && pOld->Height() == pNew->Height() && |
| "Alpha masks have different sizes (!)"); |
| |
| for(tools::Long y(0),nHeight(pOld->Height()); y < nHeight; y++) |
| { |
| Scanline pScanline = pOld->GetScanline( y ); |
| for(tools::Long x(0),nWidth(pOld->Width()); x < nWidth; x++) |
| { |
| BitmapColor aCol = pOld->GetPixelFromData(pScanline, x); |
| const double fOpOld(aCol.GetAlpha() * fFactor); |
| const double fOpNew(pNew->GetIndexFromData(pScanline, x) * fFactor); |
| aCol.SetAlpha(basegfx::fround((fOpOld * fOpNew) * 255.0)); |
| |
| pOld->SetPixelOnData(pScanline, x, aCol); |
| } |
| } |
| } |
| } |
| |
| |
| void DrawAndClipBitmap(const Point& rPos, const Size& rSize, const Bitmap& rBitmap, Bitmap & aBmp, basegfx::B2DPolyPolygon const & rClipPath) |
| { |
| ScopedVclPtrInstance< VirtualDevice > pVDev; |
| MapMode aMapMode( MapUnit::Map100thMM ); |
| aMapMode.SetOrigin( Point( -rPos.X(), -rPos.Y() ) ); |
| const Size aOutputSizePixel( pVDev->LogicToPixel( rSize, aMapMode ) ); |
| const Size aSizePixel( rBitmap.GetSizePixel() ); |
| if ( aOutputSizePixel.Width() && aOutputSizePixel.Height() ) |
| { |
| aMapMode.SetScaleX( double(aSizePixel.Width()) / aOutputSizePixel.Width() ); |
| aMapMode.SetScaleY( double(aSizePixel.Height()) / aOutputSizePixel.Height() ); |
| } |
| pVDev->SetMapMode( aMapMode ); |
| pVDev->SetOutputSizePixel( aSizePixel ); |
| pVDev->SetFillColor( COL_BLACK ); |
| const tools::PolyPolygon aClip( rClipPath ); |
| pVDev->DrawPolyPolygon( aClip ); |
| |
| // #i50672# Extract whole VDev content (to match size of rBitmap) |
| pVDev->EnableMapMode( false ); |
| const Bitmap aVDevMask(pVDev->GetBitmap(Point(), aSizePixel)); |
| |
| if(aBmp.HasAlpha()) |
| { |
| // bitmap already uses a Mask or Alpha, we need to blend that with |
| // the new masking in pVDev. |
| // need to blend in AlphaMask quality (8Bit) |
| AlphaMask fromVDev(aVDevMask); |
| BitmapScopedReadAccess pR(fromVDev); |
| BitmapScopedWriteAccess pW(aBmp); |
| assert(pR && pW); |
| |
| if(pR && pW) |
| { |
| const tools::Long nWidth(std::min(pR->Width(), pW->Width())); |
| const tools::Long nHeight(std::min(pR->Height(), pW->Height())); |
| |
| for(tools::Long nY(0); nY < nHeight; nY++) |
| { |
| Scanline pScanlineR = pR->GetScanline( nY ); |
| Scanline pScanlineW = pW->GetScanline( nY ); |
| for(tools::Long nX(0); nX < nWidth; nX++) |
| { |
| const sal_uInt8 nIndR(pR->GetIndexFromData(pScanlineR, nX)); |
| Color aCol = pW->GetColorFromData(pScanlineW, nX); |
| const sal_uInt8 nIndW(aCol.GetAlpha()); |
| |
| // these values represent alpha (255 == no, 0 == fully transparent), |
| // so to blend these we have to multiply |
| const sal_uInt8 nCombined((nIndR * nIndW) >> 8); |
| |
| aCol.SetAlpha(nCombined); |
| pW->SetPixelOnData(pScanlineW, nX, aCol); |
| } |
| } |
| } |
| |
| pR.reset(); |
| pW.reset(); |
| } |
| else |
| { |
| // no mask yet, create and add new mask. For better quality, use Alpha, |
| // this allows the drawn mask being processed with AntiAliasing (AAed) |
| aBmp = Bitmap(rBitmap, aVDevMask); |
| } |
| } |
| |
| css::uno::Sequence< sal_Int8 > GetMaskDIB(Bitmap const & aBmp) |
| { |
| if ( aBmp.HasAlpha() ) |
| { |
| SvMemoryStream aMem; |
| AlphaMask aMask = aBmp.CreateAlphaMask(); |
| // for backwards compatibility for extensions, we need to convert from alpha to transparency |
| aMask.Invert(); |
| WriteDIB(aMask.GetBitmap(), aMem, false, true); |
| return css::uno::Sequence< sal_Int8 >( static_cast<sal_Int8 const *>(aMem.GetData()), aMem.Tell() ); |
| } |
| |
| return css::uno::Sequence< sal_Int8 >(); |
| } |
| |
| /** |
| * This returns data formatted the way Cairo wants it, i.e. either CAIRO_FORMAT_ARGB32 or CAIRO_FORMAT_RGB24 |
| * |
| * @param data will be filled with alpha data, if xBitmap is alpha/transparent image |
| * @param bHasAlpha will be set to true if resulting surface has alpha |
| **/ |
| void CanvasCairoExtractBitmapData( const Bitmap & aBitmap, unsigned char*& data, bool& bHasAlpha, tools::Long& rnWidth, tools::Long& rnHeight ) |
| { |
| BitmapScopedReadAccess pBitmapReadAcc( aBitmap ); |
| const tools::Long nWidth = rnWidth = pBitmapReadAcc->Width(); |
| const tools::Long nHeight = rnHeight = pBitmapReadAcc->Height(); |
| tools::Long nX, nY; |
| bHasAlpha = aBitmap.HasAlpha(); |
| |
| /// BOTH of the cairo formats use a 32-bit pixel. |
| auto nScanlineSize = nWidth*4; |
| data = static_cast<unsigned char*>(malloc(nHeight * nScanlineSize)); |
| if (!data) |
| std::abort(); |
| |
| ::Color aColor; |
| |
| for( nY = 0; nY < nHeight; nY++ ) |
| { |
| ::Scanline pReadScan; |
| tools::Long nOff = nY * nScanlineSize; |
| |
| switch( pBitmapReadAcc->GetScanlineFormat() ) |
| { |
| case ScanlineFormat::N8BitPal: |
| assert(!bHasAlpha); |
| pReadScan = pBitmapReadAcc->GetScanline( nY ); |
| |
| for( nX = 0; nX < nWidth; nX++ ) |
| { |
| aColor = pBitmapReadAcc->GetPaletteColor(*pReadScan++); |
| #ifdef OSL_BIGENDIAN |
| data[ nOff++ ] = 0xff; |
| data[ nOff++ ] = aColor.GetRed(); |
| data[ nOff++ ] = aColor.GetGreen(); |
| data[ nOff++ ] = aColor.GetBlue(); |
| #else |
| data[ nOff++ ] = aColor.GetBlue(); |
| data[ nOff++ ] = aColor.GetGreen(); |
| data[ nOff++ ] = aColor.GetRed(); |
| data[ nOff++ ] = 0xff; |
| #endif |
| } |
| break; |
| case ScanlineFormat::N24BitTcBgr: |
| assert(!bHasAlpha); |
| pReadScan = pBitmapReadAcc->GetScanline( nY ); |
| |
| for( nX = 0; nX < nWidth; nX++ ) |
| { |
| #ifdef OSL_BIGENDIAN |
| data[ nOff + 3 ] = 0xff; |
| data[ nOff + 2 ] = *pReadScan++; |
| data[ nOff + 1 ] = *pReadScan++; |
| data[ nOff + 0 ] = *pReadScan++; |
| #else |
| data[ nOff + 0 ] = *pReadScan++; |
| data[ nOff + 1 ] = *pReadScan++; |
| data[ nOff + 2 ] = *pReadScan++; |
| data[ nOff + 3 ] = 0xff; |
| #endif |
| nOff += 4; |
| } |
| break; |
| case ScanlineFormat::N24BitTcRgb: |
| assert(!bHasAlpha); |
| pReadScan = pBitmapReadAcc->GetScanline( nY ); |
| |
| for( nX = 0; nX < nWidth; nX++ ) |
| { |
| #ifdef OSL_BIGENDIAN |
| data[ nOff + 3 ] = 0xff; |
| data[ nOff + 2 ] = *pReadScan++; |
| data[ nOff + 1 ] = *pReadScan++; |
| data[ nOff + 0 ] = *pReadScan++; |
| #else |
| data[ nOff + 0 ] = *pReadScan++; |
| data[ nOff + 1 ] = *pReadScan++; |
| data[ nOff + 2 ] = *pReadScan++; |
| data[ nOff + 3 ] = 0xff; |
| #endif |
| nOff += 4; |
| } |
| break; |
| case ScanlineFormat::N32BitTcBgra: |
| assert(bHasAlpha); |
| pReadScan = pBitmapReadAcc->GetScanline( nY ); |
| |
| // this data is already premultiplied |
| for( nX = 0; nX < nWidth; nX++ ) |
| { |
| #ifdef OSL_BIGENDIAN |
| data[ nOff++ ] = pReadScan[ 3 ]; |
| data[ nOff++ ] = pReadScan[ 2 ]; |
| data[ nOff++ ] = pReadScan[ 1 ]; |
| data[ nOff++ ] = pReadScan[ 0 ]; |
| pReadScan += 4; |
| #else |
| data[ nOff++ ] = *pReadScan++; |
| data[ nOff++ ] = *pReadScan++; |
| data[ nOff++ ] = *pReadScan++; |
| data[ nOff++ ] = *pReadScan++; |
| #endif |
| } |
| break; |
| |
| case ScanlineFormat::N32BitTcBgrx: |
| assert(!bHasAlpha); |
| pReadScan = pBitmapReadAcc->GetScanline( nY ); |
| |
| for( nX = 0; nX < nWidth; nX++ ) |
| { |
| #ifdef OSL_BIGENDIAN |
| data[ nOff++ ] = 0xff; |
| data[ nOff++ ] = pReadScan[ 2 ]; |
| data[ nOff++ ] = pReadScan[ 1 ]; |
| data[ nOff++ ] = pReadScan[ 0 ]; |
| pReadScan += 4; |
| #else |
| data[ nOff++ ] = *pReadScan++; |
| data[ nOff++ ] = *pReadScan++; |
| data[ nOff++ ] = *pReadScan++; |
| data[ nOff++ ] = 0xff; |
| pReadScan++; |
| #endif |
| } |
| break; |
| case ScanlineFormat::N32BitTcRgba: |
| assert(bHasAlpha); |
| pReadScan = pBitmapReadAcc->GetScanline( nY ); |
| |
| // this data is already premultiplied |
| for( nX = 0; nX < nWidth; nX++ ) |
| { |
| #ifdef OSL_BIGENDIAN |
| data[ nOff++ ] = *pReadScan++; |
| data[ nOff++ ] = *pReadScan++; |
| data[ nOff++ ] = *pReadScan++; |
| data[ nOff++ ] = *pReadScan++; |
| #else |
| data[ nOff++ ] = pReadScan[ 3 ]; |
| data[ nOff++ ] = pReadScan[ 2 ]; |
| data[ nOff++ ] = pReadScan[ 1 ]; |
| data[ nOff++ ] = pReadScan[ 0 ]; |
| pReadScan += 4; |
| #endif |
| } |
| break; |
| |
| case ScanlineFormat::N32BitTcRgbx: |
| assert(!bHasAlpha); |
| pReadScan = pBitmapReadAcc->GetScanline( nY ); |
| |
| for( nX = 0; nX < nWidth; nX++ ) |
| { |
| #ifdef OSL_BIGENDIAN |
| data[ nOff++ ] = 0xff; |
| data[ nOff++ ] = *pReadScan++; |
| data[ nOff++ ] = *pReadScan++; |
| data[ nOff++ ] = *pReadScan++; |
| pReadScan++; |
| #else |
| data[ nOff++ ] = pReadScan[ 2 ]; |
| data[ nOff++ ] = pReadScan[ 1 ]; |
| data[ nOff++ ] = pReadScan[ 0 ]; |
| data[ nOff++ ] = 0xff; |
| pReadScan += 4; |
| #endif |
| } |
| break; |
| default: |
| assert(false && "unknown format"); |
| } |
| } |
| } |
| |
| uno::Sequence< sal_Int8 > CanvasExtractBitmapData(Bitmap const & rBitmap, const geometry::IntegerRectangle2D& rect) |
| { |
| BitmapScopedReadAccess pReadAccess( rBitmap ); |
| assert( pReadAccess ); |
| |
| // TODO(F1): Support more formats. |
| const Size aBmpSize( rBitmap.GetSizePixel() ); |
| |
| // for the time being, always return as BGRA |
| uno::Sequence< sal_Int8 > aRes( 4*aBmpSize.Width()*aBmpSize.Height() ); |
| sal_Int8* pRes = aRes.getArray(); |
| |
| int nCurrPos(0); |
| for( tools::Long y=rect.Y1; |
| y<aBmpSize.Height() && y<rect.Y2; |
| ++y ) |
| { |
| for( tools::Long x=rect.X1; |
| x<aBmpSize.Width() && x<rect.X2; |
| ++x ) |
| { |
| BitmapColor aCol = pReadAccess->GetColor( y, x ); |
| pRes[ nCurrPos++ ] = aCol.GetRed(); |
| pRes[ nCurrPos++ ] = aCol.GetGreen(); |
| pRes[ nCurrPos++ ] = aCol.GetBlue(); |
| pRes[ nCurrPos++ ] = 255 - aCol.GetAlpha(); |
| } |
| } |
| return aRes; |
| } |
| |
| Bitmap createHistorical8x8FromArray(std::array<sal_uInt8,64> const & pArray, Color aColorPix, Color aColorBack) |
| { |
| BitmapPalette aPalette(2); |
| |
| aPalette[0] = BitmapColor(aColorBack); |
| aPalette[1] = BitmapColor(aColorPix); |
| |
| Bitmap aBitmap(Size(8, 8), vcl::PixelFormat::N8_BPP, &aPalette); |
| BitmapScopedWriteAccess pContent(aBitmap); |
| |
| for(sal_uInt16 a(0); a < 8; a++) |
| { |
| for(sal_uInt16 b(0); b < 8; b++) |
| { |
| if(pArray[(a * 8) + b]) |
| { |
| pContent->SetPixelIndex(a, b, 1); |
| } |
| else |
| { |
| pContent->SetPixelIndex(a, b, 0); |
| } |
| } |
| } |
| |
| return aBitmap; |
| } |
| |
| bool isHistorical8x8(const Bitmap& rBitmap, Color& o_rBack, Color& o_rFront) |
| { |
| bool bRet(false); |
| |
| if(!rBitmap.HasAlpha()) |
| { |
| if(8 == rBitmap.GetSizePixel().Width() && 8 == rBitmap.GetSizePixel().Height()) |
| { |
| // Historical 1bpp images are getting really historical, |
| // even to the point that e.g. the png loader actually loads |
| // them as RGB. But the pattern code in svx relies on this |
| // assumption that any 2-color 1bpp bitmap is a pattern, and so it would |
| // get confused by RGB. Try to detect if this image is really |
| // just two colors and say it's a pattern bitmap if so. |
| BitmapScopedReadAccess access(rBitmap); |
| o_rBack = access->GetColor(0,0); |
| bool foundSecondColor = false;; |
| for(tools::Long y = 0, nHeight = access->Height(); y < nHeight; ++y) |
| for(tools::Long x = 0, nWidth = access->Width(); x < nWidth; ++x) |
| { |
| if(!foundSecondColor) |
| { |
| if( access->GetColor(y,x) != o_rBack ) |
| { |
| o_rFront = access->GetColor(y,x); |
| foundSecondColor = true; |
| // Hard to know which of the two colors is the background, |
| // select the lighter one. |
| if( o_rFront.GetLuminance() > o_rBack.GetLuminance()) |
| std::swap( o_rFront, o_rBack ); |
| } |
| } |
| else |
| { |
| if( access->GetColor(y,x) != o_rBack && access->GetColor(y,x) != o_rFront) |
| return false; |
| } |
| } |
| return true; |
| } |
| } |
| |
| return bRet; |
| } |
| |
| #if ENABLE_WASM_STRIP_PREMULTIPLY |
| sal_uInt8 unpremultiply(sal_uInt8 c, sal_uInt8 a) |
| { |
| return (a == 0) ? 0 : (c * 255 + a / 2) / a; |
| } |
| |
| sal_uInt8 premultiply(sal_uInt8 c, sal_uInt8 a) |
| { |
| return (c * a + 127) / 255; |
| } |
| #else |
| sal_uInt8 unpremultiply(sal_uInt8 c, sal_uInt8 a) |
| { |
| return get_unpremultiply_table()[a][c]; |
| } |
| |
| static constexpr sal_uInt8 unpremultiplyImpl(sal_uInt8 c, sal_uInt8 a) |
| { |
| return (a == 0) ? 0 : (c * 255 + a / 2) / a; |
| } |
| |
| sal_uInt8 premultiply(sal_uInt8 c, sal_uInt8 a) |
| { |
| return get_premultiply_table()[a][c]; |
| } |
| |
| static constexpr sal_uInt8 premultiplyImpl(sal_uInt8 c, sal_uInt8 a) |
| { |
| return (c * a + 127) / 255; |
| } |
| |
| template<int... Is> static constexpr std::array<sal_uInt8, 256> make_unpremultiply_table_row_( |
| int a, std::integer_sequence<int, Is...>) |
| { |
| return {unpremultiplyImpl(Is, a)...}; |
| } |
| |
| template<int... Is> static constexpr lookup_table make_unpremultiply_table_( |
| std::integer_sequence<int, Is...>) |
| { |
| return {make_unpremultiply_table_row_(Is, std::make_integer_sequence<int, 256>{})...}; |
| } |
| |
| lookup_table const & get_unpremultiply_table() |
| { |
| static constexpr auto unpremultiply_table = make_unpremultiply_table_( |
| std::make_integer_sequence<int, 256>{}); |
| return unpremultiply_table; |
| } |
| |
| template<int... Is> static constexpr std::array<sal_uInt8, 256> make_premultiply_table_row_( |
| int a, std::integer_sequence<int, Is...>) |
| { |
| return {premultiplyImpl(Is, a)...}; |
| } |
| |
| template<int... Is> static constexpr lookup_table make_premultiply_table_( |
| std::integer_sequence<int, Is...>) |
| { |
| return {make_premultiply_table_row_(Is, std::make_integer_sequence<int, 256>{})...}; |
| } |
| |
| lookup_table const & get_premultiply_table() |
| { |
| static constexpr auto premultiply_table = make_premultiply_table_( |
| std::make_integer_sequence<int, 256>{}); |
| return premultiply_table; |
| } |
| #endif |
| |
| Bitmap GetDownsampledBitmap(Size const& rDstSizeTwip, Point const& rSrcPt, Size const& rSrcSz, |
| Bitmap const& rBmp, tools::Long nMaxBmpDPIX, tools::Long nMaxBmpDPIY) |
| { |
| Bitmap aBmp(rBmp); |
| |
| if (!aBmp.IsEmpty()) |
| { |
| const tools::Rectangle aBmpRect( Point(), aBmp.GetSizePixel() ); |
| tools::Rectangle aSrcRect( rSrcPt, rSrcSz ); |
| |
| // do cropping if necessary |
| if( aSrcRect.Intersection( aBmpRect ) != aBmpRect ) |
| { |
| if( !aSrcRect.IsEmpty() ) |
| aBmp.Crop( aSrcRect ); |
| else |
| aBmp.SetEmpty(); |
| } |
| |
| if( !aBmp.IsEmpty() ) |
| { |
| // do downsampling if necessary |
| // #103209# Normalize size (mirroring has to happen outside of this method) |
| Size aDstSizeTwip(std::abs(rDstSizeTwip.Width()), std::abs(rDstSizeTwip.Height())); |
| |
| const Size aBmpSize( aBmp.GetSizePixel() ); |
| const double fBmpPixelX = aBmpSize.Width(); |
| const double fBmpPixelY = aBmpSize.Height(); |
| const double fMaxPixelX |
| = o3tl::convert<double>(aDstSizeTwip.Width(), o3tl::Length::twip, o3tl::Length::in) |
| * nMaxBmpDPIX; |
| const double fMaxPixelY |
| = o3tl::convert<double>(aDstSizeTwip.Height(), o3tl::Length::twip, o3tl::Length::in) |
| * nMaxBmpDPIY; |
| |
| // check, if the bitmap DPI exceeds the maximum DPI (allow 4 pixel rounding tolerance) |
| if (((fBmpPixelX > (fMaxPixelX + 4)) || |
| (fBmpPixelY > (fMaxPixelY + 4))) && |
| (fBmpPixelY > 0.0) && (fMaxPixelY > 0.0)) |
| { |
| // do scaling |
| Size aNewBmpSize; |
| const double fBmpWH = fBmpPixelX / fBmpPixelY; |
| const double fMaxWH = fMaxPixelX / fMaxPixelY; |
| |
| if (fBmpWH < fMaxWH) |
| { |
| aNewBmpSize.setWidth(basegfx::fround<tools::Long>(fMaxPixelY * fBmpWH)); |
| aNewBmpSize.setHeight(basegfx::fround<tools::Long>(fMaxPixelY)); |
| } |
| else if (fBmpWH > 0.0) |
| { |
| aNewBmpSize.setWidth(basegfx::fround<tools::Long>(fMaxPixelX)); |
| aNewBmpSize.setHeight(basegfx::fround<tools::Long>(fMaxPixelX / fBmpWH)); |
| } |
| |
| if( aNewBmpSize.Width() && aNewBmpSize.Height() ) |
| aBmp.Scale(aNewBmpSize); |
| else |
| aBmp.SetEmpty(); |
| } |
| } |
| } |
| |
| return aBmp; |
| } |
| |
| BitmapColor premultiply(const BitmapColor c) |
| { |
| return BitmapColor(ColorAlpha, premultiply(c.GetRed(), c.GetAlpha()), |
| premultiply(c.GetGreen(), c.GetAlpha()), |
| premultiply(c.GetBlue(), c.GetAlpha()), c.GetAlpha()); |
| } |
| |
| BitmapColor unpremultiply(const BitmapColor c) |
| { |
| return BitmapColor(ColorAlpha, unpremultiply(c.GetRed(), c.GetAlpha()), |
| unpremultiply(c.GetGreen(), c.GetAlpha()), |
| unpremultiply(c.GetBlue(), c.GetAlpha()), c.GetAlpha()); |
| } |
| |
| } // end vcl::bitmap |
| |
| /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |