/* -*- 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/. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if ENABLE_CAIRO_CANVAS #include #endif #include #include #include #include using namespace css; using drawinglayer::primitive2d::Primitive2DSequence; using drawinglayer::primitive2d::Primitive2DReference; namespace vcl::bitmap { BitmapEx loadFromName(const OUString& rFileName, const ImageLoadFlags eFlags) { bool bSuccess = true; OUString aIconTheme; BitmapEx aBitmapEx; try { aIconTheme = Application::GetSettings().GetStyleSettings().DetermineIconTheme(); ImageTree::get().loadImage(rFileName, aIconTheme, aBitmapEx, true, eFlags); } catch (...) { bSuccess = false; } SAL_WARN_IF(!bSuccess, "vcl", "vcl::bitmap::loadFromName : could not load image " << rFileName << " via icon theme " << aIconTheme); return aBitmapEx; } void loadFromSvg(SvStream& rStream, const OUString& sPath, BitmapEx& rBitmapEx, double fScalingFactor) { uno::Reference xContext(comphelper::getProcessComponentContext()); const uno::Reference xSvgParser = graphic::SvgTools::create(xContext); std::size_t nSize = rStream.remainingSize(); std::vector aBuffer(nSize + 1); rStream.ReadBytes(aBuffer.data(), nSize); aBuffer[nSize] = 0; uno::Sequence aData(aBuffer.data(), nSize + 1); uno::Reference aInputStream(new comphelper::SequenceInputStream(aData)); const Primitive2DSequence aPrimitiveSequence = xSvgParser->getDecomposition(aInputStream, sPath); if (!aPrimitiveSequence.hasElements()) return; uno::Sequence aViewParameters; geometry::RealRectangle2D aRealRect; basegfx::B2DRange aRange; for (css::uno::Reference 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 xPrimitive2DRenderer = css::graphic::Primitive2DTools::create(xContext); const css::uno::Reference xBitmap( xPrimitive2DRenderer->rasterize(aPrimitiveSequence, aViewParameters, nDPI, nDPI, aRealRect, 256*256)); if (xBitmap.is()) { const css::uno::Reference xIntBmp(xBitmap, uno::UNO_QUERY_THROW); rBitmapEx = vcl::unotools::bitmapExFromXBitmap(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 */ BitmapEx 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 BitmapEx(); std::optional pAlphaMask; BitmapScopedWriteAccess xMaskAcc; if (nBitCount == 32) { pAlphaMask.emplace( Size(nWidth, nHeight) ); xMaskAcc = *pAlphaMask; } 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; } if (nBitCount == 32) { p = pData + (y * nStride) + 3; Scanline pMaskScanLine = xMaskAcc->GetScanline(y); for (tools::Long x = 0; x < nWidth; ++x) { // FIXME this parameter is badly named const sal_uInt8 nValue = bReverseAlpha ? *p : 0xff - *p; xMaskAcc->SetPixelOnData(pMaskScanLine, x, BitmapColor(nValue)); p += 4; } } } } // Avoid further bitmap use with unfinished write access pWrite.reset(); xMaskAcc.reset(); if (nBitCount == 32) return BitmapEx(aBmp, *pAlphaMask); else return BitmapEx(aBmp); } /** Copy block of image data into the bitmap. Assumes that the Bitmap has been constructed with the desired size. */ BitmapEx 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 BitmapEx(); std::optional pAlphaMask; BitmapScopedWriteAccess xMaskAcc; if (nBitCount == 32) { pAlphaMask.emplace( rawBitmap.maSize ); xMaskAcc = *pAlphaMask; } auto nHeight = rawBitmap.maSize.getHeight(); auto nWidth = rawBitmap.maSize.getWidth(); auto nStride = nWidth * nBitCount / 8; 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; } if (nBitCount == 32) { p = rawBitmap.mpData.get() + (y * nStride) + 3; Scanline pMaskScanLine = xMaskAcc->GetScanline(y); for (tools::Long x = 0; x < nWidth; ++x) { xMaskAcc->SetPixelOnData(pMaskScanLine, x, BitmapColor(*p)); p += 4; } } } xMaskAcc.reset(); pWrite.reset(); if (nBitCount == 32) return BitmapEx(aBmp, *pAlphaMask); else return BitmapEx(aBmp); } #if ENABLE_CAIRO_CANVAS BitmapEx* 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 nullptr; // 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 aRGB(aSize, vcl::PixelFormat::N24_BPP); ::AlphaMask aMask( aSize ); BitmapScopedWriteAccess pRGBWrite(aRGB); assert(pRGBWrite); if (!pRGBWrite) return nullptr; BitmapScopedWriteAccess pMaskWrite(aMask); assert(pMaskWrite); if (!pMaskWrite) return nullptr; 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(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(nAlpha, nR); nG = vcl::bitmap::unpremultiply(nAlpha, nG); nB = vcl::bitmap::unpremultiply(nAlpha, nB); #else nR = unpremultiply_table[nAlpha][nR]; nG = unpremultiply_table[nAlpha][nG]; nB = unpremultiply_table[nAlpha][nB]; #endif } pRGBWrite->SetPixel( y, x, BitmapColor( nR, nG, nB ) ); pMaskWrite->SetPixelIndex( y, x, nAlpha ); pPix++; } } // ignore potential errors above. will get caller a // uniformly white bitmap, but not that there would // be error handling in calling code ... ::BitmapEx *pBitmapEx = new ::BitmapEx( aRGB, aMask ); cairo_destroy( pCairo ); cairo_surface_destroy( pPixels ); return pBitmapEx; } #endif BitmapEx CanvasTransformBitmap( const BitmapEx& rBitmap, const ::basegfx::B2DHomMatrix& rTransform, ::basegfx::B2DRectangle const & rDestRect, ::basegfx::B2DHomMatrix const & rLocalTransform ) { const Size aDestBmpSize( ::basegfx::fround( rDestRect.getWidth() ), ::basegfx::fround( rDestRect.getHeight() ) ); if( aDestBmpSize.IsEmpty() ) return BitmapEx(); const Size aBmpSize( rBitmap.GetSizePixel() ); Bitmap aSrcBitmap( rBitmap.GetBitmap() ); Bitmap aSrcAlpha; // differentiate mask and alpha channel (on-off // vs. multi-level transparency) if( rBitmap.IsAlpha() ) { aSrcAlpha = rBitmap.GetAlphaMask().GetBitmap(); } BitmapScopedReadAccess pReadAccess( aSrcBitmap ); BitmapScopedReadAccess pAlphaReadAccess; if (rBitmap.IsAlpha()) pAlphaReadAccess = aSrcAlpha; if( !pReadAccess || (!pAlphaReadAccess && rBitmap.IsAlpha()) ) { // 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( rBitmap.IsAlpha() ) { // 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, aSrcBitmap.getPixelFormat(), &pReadAccess->GetPalette()); Bitmap aDstAlpha( AlphaMask( aDestBmpSize ).GetBitmap() ); { // 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 ); BitmapScopedWriteAccess pAlphaWriteAccess( aDstAlpha ); if( pWriteAccess.get() != nullptr && pAlphaWriteAccess.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; yGetScanline( y ); Scanline pScanAlpha = pAlphaWriteAccess->GetScanline( y ); // Handling alpha and mask just the same... for( tools::Long x=0; x( aPoint.getX() ) ); const tools::Long nSrcY( ::basegfx::fround( aPoint.getY() ) ); if( nSrcX < 0 || nSrcX >= aBmpSize.Width() || nSrcY < 0 || nSrcY >= aBmpSize.Height() ) { pAlphaWriteAccess->SetPixelOnData( pScanAlpha, x, BitmapColor(0) ); } else { const sal_uInt8 cAlphaIdx = pAlphaReadAccess->GetPixelIndex( nSrcY, nSrcX ); pAlphaWriteAccess->SetPixelOnData( pScanAlpha, x, BitmapColor(aAlphaMap[ cAlphaIdx ]) ); pWriteAccess->SetPixelOnData( pScan, x, pReadAccess->GetPixel( nSrcY, nSrcX ) ); } } } else { Scanline pScan = pWriteAccess->GetScanline( y ); Scanline pScanAlpha = pAlphaWriteAccess->GetScanline( y ); for( tools::Long x=0; x( aPoint.getX() ) ); const tools::Long nSrcY( ::basegfx::fround( aPoint.getY() ) ); if( nSrcX < 0 || nSrcX >= aBmpSize.Width() || nSrcY < 0 || nSrcY >= aBmpSize.Height() ) { pAlphaWriteAccess->SetPixelOnData( pScanAlpha, x, BitmapColor(0) ); } else { pAlphaWriteAccess->SetPixelOnData( pScanAlpha, x, BitmapColor(255) ); pWriteAccess->SetPixelOnData( pScan, x, pReadAccess->GetPixel( nSrcY, nSrcX ) ); } } } } } else { // TODO(E2): Error handling! ENSURE_OR_THROW( false, "transformBitmap(): could not access bitmap" ); } } return BitmapEx(aDstBitmap, AlphaMask(aDstAlpha)); } void DrawAlphaBitmapAndAlphaGradient(BitmapEx & rBitmapEx, bool bFixedTransparence, float fTransparence, AlphaMask & rNewMask) { // mix existing and new alpha mask AlphaMask aOldMask; if(rBitmapEx.IsAlpha()) { aOldMask = rBitmapEx.GetAlphaMask(); } { BitmapScopedWriteAccess pOld(aOldMask); assert(pOld && "Got no access to old alpha mask (!)"); const double fFactor(1.0 / 255.0); if(bFixedTransparence) { const double fOpNew(1.0 - fTransparence); for(tools::Long y(0); y < pOld->Height(); y++) { Scanline pScanline = pOld->GetScanline( y ); for(tools::Long x(0); x < pOld->Width(); x++) { const double fOpOld(pOld->GetIndexFromData(pScanline, x) * fFactor); const sal_uInt8 aCol(basegfx::fround((fOpOld * fOpNew) * 255.0)); pOld->SetPixelOnData(pScanline, x, BitmapColor(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); y < pOld->Height(); y++) { Scanline pScanline = pOld->GetScanline( y ); for(tools::Long x(0); x < pOld->Width(); x++) { const double fOpOld(pOld->GetIndexFromData(pScanline, x) * fFactor); const double fOpNew(pNew->GetIndexFromData(pScanline, x) * fFactor); const sal_uInt8 aCol(basegfx::fround((fOpOld * fOpNew) * 255.0)); pOld->SetPixelOnData(pScanline, x, BitmapColor(aCol)); } } } } // apply combined bitmap as mask rBitmapEx = BitmapEx(rBitmapEx.GetBitmap(), aOldMask); } void DrawAndClipBitmap(const Point& rPos, const Size& rSize, const BitmapEx& rBitmap, BitmapEx & aBmpEx, 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( Fraction( aSizePixel.Width(), aOutputSizePixel.Width() ) ); aMapMode.SetScaleY( Fraction( 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(aBmpEx.IsAlpha()) { // 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); AlphaMask fromBmpEx(aBmpEx.GetAlphaMask()); BitmapScopedReadAccess pR(fromVDev); BitmapScopedWriteAccess pW(fromBmpEx); 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)); const sal_uInt8 nIndW(pW->GetIndexFromData(pScanlineW, nX)); // these values represent alpha (255 == no, 0 == fully transparent), // so to blend these we have to multiply const sal_uInt8 nCombined((nIndR * nIndW) >> 8); pW->SetPixelOnData(pScanlineW, nX, BitmapColor(nCombined)); } } } pR.reset(); pW.reset(); aBmpEx = BitmapEx(aBmpEx.GetBitmap(), fromBmpEx); } else { // no mask yet, create and add new mask. For better quality, use Alpha, // this allows the drawn mask being processed with AntiAliasing (AAed) aBmpEx = BitmapEx(rBitmap.GetBitmap(), aVDevMask); } } css::uno::Sequence< sal_Int8 > GetMaskDIB(BitmapEx const & aBmpEx) { if ( aBmpEx.IsAlpha() ) { SvMemoryStream aMem; WriteDIB(aBmpEx.GetAlphaMask().GetBitmap(), aMem, false, true); return css::uno::Sequence< sal_Int8 >( static_cast(aMem.GetData()), aMem.Tell() ); } return css::uno::Sequence< sal_Int8 >(); } static bool readAlpha( BitmapReadAccess const * pAlphaReadAcc, tools::Long nY, const tools::Long nWidth, unsigned char* data, tools::Long nOff ) { bool bIsAlpha = false; tools::Long nX; int nAlpha; Scanline pReadScan; nOff += 3; switch( pAlphaReadAcc->GetScanlineFormat() ) { case ScanlineFormat::N8BitPal: pReadScan = pAlphaReadAcc->GetScanline( nY ); for( nX = 0; nX < nWidth; nX++ ) { BitmapColor const& rColor( pAlphaReadAcc->GetPaletteColor(*pReadScan)); pReadScan++; nAlpha = data[ nOff ] = rColor.GetIndex(); if( nAlpha != 255 ) bIsAlpha = true; nOff += 4; } break; default: SAL_INFO( "canvas.cairo", "fallback to GetColor for alpha - slow, format: " << static_cast(pAlphaReadAcc->GetScanlineFormat()) ); for( nX = 0; nX < nWidth; nX++ ) { nAlpha = data[ nOff ] = pAlphaReadAcc->GetColor( nY, nX ).GetIndex(); if( nAlpha != 255 ) bIsAlpha = true; nOff += 4; } } return bIsAlpha; } /** * @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( BitmapEx const & aBmpEx, Bitmap & aBitmap, unsigned char*& data, bool& bHasAlpha, tools::Long& rnWidth, tools::Long& rnHeight ) { AlphaMask aAlpha = aBmpEx.GetAlphaMask(); BitmapScopedReadAccess pBitmapReadAcc( aBitmap ); BitmapScopedReadAccess pAlphaReadAcc; const tools::Long nWidth = rnWidth = pBitmapReadAcc->Width(); const tools::Long nHeight = rnHeight = pBitmapReadAcc->Height(); tools::Long nX, nY; bool bIsAlpha = false; if( aBmpEx.IsAlpha() ) pAlphaReadAcc = aAlpha; data = static_cast(malloc( nWidth*nHeight*4 )); tools::Long nOff = 0; ::Color aColor; unsigned int nAlpha = 255; #if !ENABLE_WASM_STRIP_PREMULTIPLY vcl::bitmap::lookup_table const & premultiply_table = vcl::bitmap::get_premultiply_table(); #endif for( nY = 0; nY < nHeight; nY++ ) { ::Scanline pReadScan; switch( pBitmapReadAcc->GetScanlineFormat() ) { case ScanlineFormat::N8BitPal: pReadScan = pBitmapReadAcc->GetScanline( nY ); if( pAlphaReadAcc ) if( readAlpha( pAlphaReadAcc.get(), nY, nWidth, data, nOff ) ) bIsAlpha = true; for( nX = 0; nX < nWidth; nX++ ) { #ifdef OSL_BIGENDIAN if( pAlphaReadAcc ) nAlpha = data[ nOff++ ]; else nAlpha = data[ nOff++ ] = 255; #else if( pAlphaReadAcc ) nAlpha = data[ nOff + 3 ]; else nAlpha = data[ nOff + 3 ] = 255; #endif aColor = pBitmapReadAcc->GetPaletteColor(*pReadScan++); #ifdef OSL_BIGENDIAN #if ENABLE_WASM_STRIP_PREMULTIPLY data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, aColor.GetRed()); data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, aColor.GetGreen()); data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, aColor.GetBlue()); #else data[ nOff++ ] = premultiply_table[nAlpha][aColor.GetRed()]; data[ nOff++ ] = premultiply_table[nAlpha][aColor.GetGreen()]; data[ nOff++ ] = premultiply_table[nAlpha][aColor.GetBlue()]; #endif #else #if ENABLE_WASM_STRIP_PREMULTIPLY data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, aColor.GetBlue()); data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, aColor.GetGreen()); data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, aColor.GetRed()); #else data[ nOff++ ] = premultiply_table[nAlpha][aColor.GetBlue()]; data[ nOff++ ] = premultiply_table[nAlpha][aColor.GetGreen()]; data[ nOff++ ] = premultiply_table[nAlpha][aColor.GetRed()]; #endif nOff++; #endif } break; case ScanlineFormat::N24BitTcBgr: pReadScan = pBitmapReadAcc->GetScanline( nY ); if( pAlphaReadAcc ) if( readAlpha( pAlphaReadAcc.get(), nY, nWidth, data, nOff ) ) bIsAlpha = true; for( nX = 0; nX < nWidth; nX++ ) { #ifdef OSL_BIGENDIAN if( pAlphaReadAcc ) nAlpha = data[ nOff ]; else nAlpha = data[ nOff ] = 255; #if ENABLE_WASM_STRIP_PREMULTIPLY data[ nOff + 3 ] = vcl::bitmap::premultiply(nAlpha, *pReadScan++); data[ nOff + 2 ] = vcl::bitmap::premultiply(nAlpha, *pReadScan++); data[ nOff + 1 ] = vcl::bitmap::premultiply(nAlpha, *pReadScan++); #else data[ nOff + 3 ] = premultiply_table[nAlpha][*pReadScan++]; data[ nOff + 2 ] = premultiply_table[nAlpha][*pReadScan++]; data[ nOff + 1 ] = premultiply_table[nAlpha][*pReadScan++]; #endif nOff += 4; #else if( pAlphaReadAcc ) nAlpha = data[ nOff + 3 ]; else nAlpha = data[ nOff + 3 ] = 255; #if ENABLE_WASM_STRIP_PREMULTIPLY data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, *pReadScan++); data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, *pReadScan++); data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, *pReadScan++); #else data[ nOff++ ] = premultiply_table[nAlpha][*pReadScan++]; data[ nOff++ ] = premultiply_table[nAlpha][*pReadScan++]; data[ nOff++ ] = premultiply_table[nAlpha][*pReadScan++]; #endif nOff++; #endif } break; case ScanlineFormat::N24BitTcRgb: pReadScan = pBitmapReadAcc->GetScanline( nY ); if( pAlphaReadAcc ) if( readAlpha( pAlphaReadAcc.get(), nY, nWidth, data, nOff ) ) bIsAlpha = true; for( nX = 0; nX < nWidth; nX++ ) { #ifdef OSL_BIGENDIAN if( pAlphaReadAcc ) nAlpha = data[ nOff++ ]; else nAlpha = data[ nOff++ ] = 255; #if ENABLE_WASM_STRIP_PREMULTIPLY data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, *pReadScan++); data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, *pReadScan++); data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, *pReadScan++); #else data[ nOff++ ] = premultiply_table[nAlpha][*pReadScan++]; data[ nOff++ ] = premultiply_table[nAlpha][*pReadScan++]; data[ nOff++ ] = premultiply_table[nAlpha][*pReadScan++]; #endif #else if( pAlphaReadAcc ) nAlpha = data[ nOff + 3 ]; else nAlpha = data[ nOff + 3 ] = 255; #if ENABLE_WASM_STRIP_PREMULTIPLY data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, pReadScan[ 2 ]); data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, pReadScan[ 1 ]); data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, pReadScan[ 0 ]); #else data[ nOff++ ] = premultiply_table[nAlpha][pReadScan[ 2 ]]; data[ nOff++ ] = premultiply_table[nAlpha][pReadScan[ 1 ]]; data[ nOff++ ] = premultiply_table[nAlpha][pReadScan[ 0 ]]; #endif pReadScan += 3; nOff++; #endif } break; case ScanlineFormat::N32BitTcBgra: pReadScan = pBitmapReadAcc->GetScanline( nY ); if( pAlphaReadAcc ) if( readAlpha( pAlphaReadAcc.get(), nY, nWidth, data, nOff ) ) bIsAlpha = true; for( nX = 0; nX < nWidth; nX++ ) { #ifdef OSL_BIGENDIAN if( pAlphaReadAcc ) nAlpha = data[ nOff++ ]; else nAlpha = data[ nOff++ ] = 255; #if ENABLE_WASM_STRIP_PREMULTIPLY data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, pReadScan[ 2 ]); data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, pReadScan[ 1 ]); data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, pReadScan[ 0 ]); #else data[ nOff++ ] = premultiply_table[nAlpha][pReadScan[ 2 ]]; data[ nOff++ ] = premultiply_table[nAlpha][pReadScan[ 1 ]]; data[ nOff++ ] = premultiply_table[nAlpha][pReadScan[ 0 ]]; #endif pReadScan += 4; #else if( pAlphaReadAcc ) nAlpha = data[ nOff + 3 ]; else nAlpha = data[ nOff + 3 ] = 255; #if ENABLE_WASM_STRIP_PREMULTIPLY data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, *pReadScan++); data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, *pReadScan++); data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, *pReadScan++); #else data[ nOff++ ] = premultiply_table[nAlpha][*pReadScan++]; data[ nOff++ ] = premultiply_table[nAlpha][*pReadScan++]; data[ nOff++ ] = premultiply_table[nAlpha][*pReadScan++]; #endif pReadScan++; nOff++; #endif } break; case ScanlineFormat::N32BitTcRgba: pReadScan = pBitmapReadAcc->GetScanline( nY ); if( pAlphaReadAcc ) if( readAlpha( pAlphaReadAcc.get(), nY, nWidth, data, nOff ) ) bIsAlpha = true; for( nX = 0; nX < nWidth; nX++ ) { #ifdef OSL_BIGENDIAN if( pAlphaReadAcc ) nAlpha = data[ nOff ++ ]; else nAlpha = data[ nOff ++ ] = 255; #if ENABLE_WASM_STRIP_PREMULTIPLY data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, *pReadScan++); data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, *pReadScan++); data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, *pReadScan++); #else data[ nOff++ ] = premultiply_table[nAlpha][*pReadScan++]; data[ nOff++ ] = premultiply_table[nAlpha][*pReadScan++]; data[ nOff++ ] = premultiply_table[nAlpha][*pReadScan++]; #endif pReadScan++; #else if( pAlphaReadAcc ) nAlpha = data[ nOff + 3 ]; else nAlpha = data[ nOff + 3 ] = 255; #if ENABLE_WASM_STRIP_PREMULTIPLY data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, pReadScan[ 2 ]); data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, pReadScan[ 1 ]); data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, pReadScan[ 0 ]); #else data[ nOff++ ] = premultiply_table[nAlpha][pReadScan[ 2 ]]; data[ nOff++ ] = premultiply_table[nAlpha][pReadScan[ 1 ]]; data[ nOff++ ] = premultiply_table[nAlpha][pReadScan[ 0 ]]; #endif pReadScan += 4; nOff++; #endif } break; default: SAL_INFO( "canvas.cairo", "fallback to GetColor - slow, format: " << static_cast(pBitmapReadAcc->GetScanlineFormat()) ); if( pAlphaReadAcc ) if( readAlpha( pAlphaReadAcc.get(), nY, nWidth, data, nOff ) ) bIsAlpha = true; for( nX = 0; nX < nWidth; nX++ ) { aColor = pBitmapReadAcc->GetColor( nY, nX ); // cairo need premultiplied color values // TODO(rodo) handle endianness #ifdef OSL_BIGENDIAN if( pAlphaReadAcc ) nAlpha = data[ nOff++ ]; else nAlpha = data[ nOff++ ] = 255; #if ENABLE_WASM_STRIP_PREMULTIPLY data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, aColor.GetRed()); data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, aColor.GetGreen()); data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, aColor.GetBlue()); #else data[ nOff++ ] = premultiply_table[nAlpha][aColor.GetRed()]; data[ nOff++ ] = premultiply_table[nAlpha][aColor.GetGreen()]; data[ nOff++ ] = premultiply_table[nAlpha][aColor.GetBlue()]; #endif #else if( pAlphaReadAcc ) nAlpha = data[ nOff + 3 ]; else nAlpha = data[ nOff + 3 ] = 255; #if ENABLE_WASM_STRIP_PREMULTIPLY data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, aColor.GetBlue()); data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, aColor.GetGreen()); data[ nOff++ ] = vcl::bitmap::premultiply(nAlpha, aColor.GetRed()); #else data[ nOff++ ] = premultiply_table[nAlpha][aColor.GetBlue()]; data[ nOff++ ] = premultiply_table[nAlpha][aColor.GetGreen()]; data[ nOff++ ] = premultiply_table[nAlpha][aColor.GetRed()]; #endif nOff ++; #endif } } } bHasAlpha = bIsAlpha; } uno::Sequence< sal_Int8 > CanvasExtractBitmapData(BitmapEx const & rBitmapEx, const geometry::IntegerRectangle2D& rect) { Bitmap aBitmap( rBitmapEx.GetBitmap() ); Bitmap aAlpha( rBitmapEx.GetAlphaMask().GetBitmap() ); BitmapScopedReadAccess pReadAccess( aBitmap ); BitmapScopedReadAccess pAlphaReadAccess; if (!aAlpha.IsEmpty()) pAlphaReadAccess = aAlpha; assert( pReadAccess ); // TODO(F1): Support more formats. const Size aBmpSize( aBitmap.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; yGetScanline( y ); for( tools::Long x=rect.X1; xGetColor( y, x ).GetRed(); pRes[ nCurrPos++ ] = pReadAccess->GetColor( y, x ).GetGreen(); pRes[ nCurrPos++ ] = pReadAccess->GetColor( y, x ).GetBlue(); pRes[ nCurrPos++ ] = 255 - pAlphaReadAccess->GetIndexFromData( pScanlineReadAlpha, x ); } } else { for( tools::Long x=rect.X1; xGetColor( y, x ).GetRed(); pRes[ nCurrPos++ ] = pReadAccess->GetColor( y, x ).GetGreen(); pRes[ nCurrPos++ ] = pReadAccess->GetColor( y, x ).GetBlue(); pRes[ nCurrPos++ ] = sal_uInt8(255); } } } return aRes; } BitmapEx createHistorical8x8FromArray(std::array 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 BitmapEx(aBitmap); } bool isHistorical8x8(const BitmapEx& rBitmapEx, Color& o_rBack, Color& o_rFront) { bool bRet(false); if(!rBitmapEx.IsAlpha()) { Bitmap aBitmap(rBitmapEx.GetBitmap()); if(8 == aBitmap.GetSizePixel().Width() && 8 == aBitmap.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(aBitmap); o_rBack = access->GetColor(0,0); bool foundSecondColor = false;; for(tools::Long y = 0; y < access->Height(); ++y) for(tools::Long x = 0; x < access->Width(); ++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 static constexpr std::array make_unpremultiply_table_row_( int a, std::integer_sequence) { return {unpremultiplyImpl(Is, a)...}; } template static constexpr lookup_table make_unpremultiply_table_( std::integer_sequence) { return {make_unpremultiply_table_row_(Is, std::make_integer_sequence{})...}; } lookup_table const & get_unpremultiply_table() { static constexpr auto unpremultiply_table = make_unpremultiply_table_( std::make_integer_sequence{}); return unpremultiply_table; } template static constexpr std::array make_premultiply_table_row_( int a, std::integer_sequence) { return {premultiplyImpl(Is, a)...}; } template static constexpr lookup_table make_premultiply_table_( std::integer_sequence) { return {make_premultiply_table_row_(Is, std::make_integer_sequence{})...}; } lookup_table const & get_premultiply_table() { static constexpr auto premultiply_table = make_premultiply_table_( std::make_integer_sequence{}); return premultiply_table; } #endif bool convertBitmap32To24Plus8(BitmapEx const & rInput, BitmapEx & rResult) { Bitmap aBitmap(rInput.GetBitmap()); if (aBitmap.getPixelFormat() != vcl::PixelFormat::N32_BPP) return false; Size aSize = aBitmap.GetSizePixel(); Bitmap aResultBitmap(aSize, vcl::PixelFormat::N24_BPP); AlphaMask aResultAlpha(aSize); { BitmapScopedWriteAccess pResultBitmapAccess(aResultBitmap); BitmapScopedWriteAccess pResultAlphaAccess(aResultAlpha); BitmapScopedReadAccess pReadAccess(aBitmap); for (tools::Long nY = 0; nY < aSize.Height(); ++nY) { Scanline aResultScan = pResultBitmapAccess->GetScanline(nY); Scanline aResultScanAlpha = pResultAlphaAccess->GetScanline(nY); Scanline aReadScan = pReadAccess->GetScanline(nY); for (tools::Long nX = 0; nX < aSize.Width(); ++nX) { const BitmapColor aColor = pReadAccess->GetPixelFromData(aReadScan, nX); BitmapColor aResultColor(aColor.GetRed(), aColor.GetGreen(), aColor.GetBlue()); BitmapColor aResultColorAlpha(aColor.GetAlpha(), aColor.GetAlpha(), aColor.GetAlpha()); pResultBitmapAccess->SetPixelOnData(aResultScan, nX, aResultColor); pResultAlphaAccess->SetPixelOnData(aResultScanAlpha, nX, aResultColorAlpha); } } } if (rInput.IsAlpha()) rResult = BitmapEx(aResultBitmap, rInput.GetAlphaMask()); else rResult = BitmapEx(aResultBitmap, aResultAlpha); return true; } 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(aDstSizeTwip.Width(), o3tl::Length::twip, o3tl::Length::in) * nMaxBmpDPIX; const double fMaxPixelY = o3tl::convert(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(fMaxPixelY * fBmpWH)); aNewBmpSize.setHeight(basegfx::fround(fMaxPixelY)); } else if (fBmpWH > 0.0) { aNewBmpSize.setWidth(basegfx::fround(fMaxPixelX)); aNewBmpSize.setHeight(basegfx::fround(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: */