/* -*- 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 #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 (Primitive2DReference 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) */ BitmapEx CreateFromData( sal_uInt8 const *pData, sal_Int32 nWidth, sal_Int32 nHeight, sal_Int32 nStride, sal_uInt16 nBitCount ) { assert(nStride >= (nWidth * nBitCount / 8)); assert( nBitCount == 1 || nBitCount == 24 || nBitCount == 32); Bitmap aBmp( Size( nWidth, nHeight ), nBitCount ); BitmapScopedWriteAccess pWrite(aBmp); assert(pWrite.get()); if( !pWrite ) return BitmapEx(); std::unique_ptr pAlphaMask; AlphaScopedWriteAccess xMaskAcc; if (nBitCount == 32) { pAlphaMask.reset( new AlphaMask( Size(nWidth, nHeight) ) ); xMaskAcc = AlphaScopedWriteAccess(*pAlphaMask); } if (nBitCount == 1) { for( long y = 0; y < nHeight; ++y ) { Scanline pScanline = pWrite->GetScanline(y); for (long x = 0; x < nWidth; ++x) { sal_uInt8 const *p = pData + y * nStride / 8; int bitIndex = (y * nStride) % 8; pWrite->SetPixelOnData(pScanline, x, BitmapColor((*p >> bitIndex) & 1)); } } } else { for( long y = 0; y < nHeight; ++y ) { sal_uInt8 const *p = pData + (y * nStride); Scanline pScanline = pWrite->GetScanline(y); for (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 = pData + (y * nStride) + 3; Scanline pMaskScanLine = xMaskAcc->GetScanline(y); for (long x = 0; x < nWidth; ++x) { xMaskAcc->SetPixelOnData(pMaskScanLine, x, BitmapColor(*p)); p += 4; } } } } 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); Bitmap aBmp( rawBitmap.maSize, nBitCount ); BitmapScopedWriteAccess pWrite(aBmp); assert(pWrite.get()); if( !pWrite ) return BitmapEx(); std::unique_ptr pAlphaMask; AlphaScopedWriteAccess xMaskAcc; if (nBitCount == 32) { pAlphaMask.reset( new AlphaMask( rawBitmap.maSize ) ); xMaskAcc = AlphaScopedWriteAccess(*pAlphaMask); } auto nHeight = rawBitmap.maSize.getHeight(); auto nWidth = rawBitmap.maSize.getWidth(); auto nStride = nWidth * nBitCount / 8; for( long y = 0; y < nHeight; ++y ) { sal_uInt8 const *p = rawBitmap.mpData.get() + (y * nStride); Scanline pScanline = pWrite->GetScanline(y); for (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 (long x = 0; x < nWidth; ++x) { xMaskAcc->SetPixelOnData(pMaskScanLine, x, BitmapColor(*p)); p += 4; } } } 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. #if CAIRO_VERSION >= CAIRO_VERSION_ENCODE(1, 12, 0) cairo_surface_t *pPixels = cairo_surface_create_similar_image(pSurface, #else cairo_surface_t *pPixels = cairo_image_surface_create( #endif 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, 24 ); ::AlphaMask aMask( aSize ); BitmapScopedWriteAccess pRGBWrite(aRGB); assert(pRGBWrite); if (!pRGBWrite) return nullptr; AlphaScopedWriteAccess 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 ); vcl::bitmap::lookup_table unpremultiply_table = vcl::bitmap::get_unpremultiply_table(); for( long y = 0; y < aSize.Height(); y++ ) { sal_uInt32 *pPix = reinterpret_cast(pSrc + nStride * y); for( 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 nR = unpremultiply_table[nAlpha][nR]; nG = unpremultiply_table[nAlpha][nG]; nB = unpremultiply_table[nAlpha][nB]; } pRGBWrite->SetPixel( y, x, BitmapColor( nR, nG, nB ) ); pMaskWrite->SetPixelIndex( y, x, 255 - 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 aBmpSize( rBitmap.GetSizePixel() ); Bitmap aSrcBitmap( rBitmap.GetBitmap() ); Bitmap aSrcAlpha; // differentiate mask and alpha channel (on-off // vs. multi-level transparency) if( rBitmap.IsTransparent() ) { if( rBitmap.IsAlpha() ) aSrcAlpha = rBitmap.GetAlpha().GetBitmap(); else aSrcAlpha = rBitmap.GetMask(); } Bitmap::ScopedReadAccess pReadAccess( aSrcBitmap ); Bitmap::ScopedReadAccess pAlphaReadAccess( rBitmap.IsTransparent() ? aSrcAlpha.AcquireReadAccess() : nullptr, aSrcAlpha ); if( pReadAccess.get() == nullptr || (pAlphaReadAccess.get() == nullptr && rBitmap.IsTransparent()) ) { // 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.IsTransparent() ) { 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 { // mask transparency - determine used palette colors const BitmapColor& rCol0( pAlphaReadAccess->GetPaletteColor( 0 ) ); const BitmapColor& rCol1( pAlphaReadAccess->GetPaletteColor( 1 ) ); // shortcut for true luminance calculation // (assumes that palette is grey-level) aAlphaMap[0] = rCol0.GetRed(); aAlphaMap[1] = rCol1.GetRed(); } } // else: mapping table is not used const Size aDestBmpSize( ::basegfx::fround( rDestRect.getWidth() ), ::basegfx::fround( rDestRect.getHeight() ) ); if( aDestBmpSize.IsEmpty() ) return BitmapEx(); Bitmap aDstBitmap( aDestBmpSize, aSrcBitmap.GetBitCount(), &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( long y=0; yGetScanline( y ); Scanline pScanAlpha = pAlphaWriteAccess->GetScanline( y ); // Handling alpha and mask just the same... for( long x=0; x= aBmpSize.Width() || nSrcY < 0 || nSrcY >= aBmpSize.Height() ) { pAlphaWriteAccess->SetPixelOnData( pScanAlpha, x, BitmapColor(255) ); } 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( long x=0; x= aBmpSize.Width() || nSrcY < 0 || nSrcY >= aBmpSize.Height() ) { pAlphaWriteAccess->SetPixelOnData( pScanAlpha, x, BitmapColor(255) ); } else { pAlphaWriteAccess->SetPixelOnData( pScanAlpha, x, BitmapColor(0) ); 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.GetAlpha(); } else if(TransparentType::Bitmap == rBitmapEx.GetTransparentType()) { aOldMask = rBitmapEx.GetMask(); } else if(TransparentType::Color == rBitmapEx.GetTransparentType()) { aOldMask = rBitmapEx.GetBitmap().CreateMask(rBitmapEx.GetTransparentColor()); } { AlphaScopedWriteAccess 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(long y(0); y < pOld->Height(); y++) { Scanline pScanline = pOld->GetScanline( y ); for(long x(0); x < pOld->Width(); x++) { const double fOpOld(1.0 - (pOld->GetIndexFromData(pScanline, x) * fFactor)); const sal_uInt8 aCol(basegfx::fround((1.0 - (fOpOld * fOpNew)) * 255.0)); pOld->SetPixelOnData(pScanline, x, BitmapColor(aCol)); } } } else { AlphaMask::ScopedReadAccess 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(long y(0); y < pOld->Height(); y++) { Scanline pScanline = pOld->GetScanline( y ); for(long x(0); x < pOld->Width(); x++) { const double fOpOld(1.0 - (pOld->GetIndexFromData(pScanline, x) * fFactor)); const double fOpNew(1.0 - (pNew->GetIndexFromData(pScanline, x) * fFactor)); const sal_uInt8 aCol(basegfx::fround((1.0 - (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.IsTransparent()) { // bitmap already uses a Mask or Alpha, we need to blend that with // the new masking in pVDev if(aBmpEx.IsAlpha()) { // need to blend in AlphaMask quality (8Bit) AlphaMask fromVDev(aVDevMask); AlphaMask fromBmpEx(aBmpEx.GetAlpha()); AlphaMask::ScopedReadAccess pR(fromVDev); AlphaScopedWriteAccess pW(fromBmpEx); if(pR && pW) { const long nWidth(std::min(pR->Width(), pW->Width())); const long nHeight(std::min(pR->Height(), pW->Height())); for(long nY(0); nY < nHeight; nY++) { Scanline pScanlineR = pR->GetScanline( nY ); Scanline pScanlineW = pW->GetScanline( nY ); for(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 transparency (0 == no, 255 == fully transparent), // so to blend these we have to multiply the inverse (opacity) // and re-invert the result to transparence const sal_uInt8 nCombined(0x00ff - (((0x00ff - nIndR) * (0x00ff - nIndW)) >> 8)); pW->SetPixelOnData(pScanlineW, nX, BitmapColor(nCombined)); } } } pR.reset(); pW.reset(); aBmpEx = BitmapEx(aBmpEx.GetBitmap(), fromBmpEx); } else { // need to blend in Mask quality (1Bit) Bitmap aMask(aVDevMask.CreateMask(COL_WHITE)); if ( rBitmap.GetTransparentColor() == COL_WHITE ) { aMask.CombineSimple( rBitmap.GetMask(), BmpCombine::Or ); } else { aMask.CombineSimple( rBitmap.GetMask(), BmpCombine::And ); } aBmpEx = BitmapEx( rBitmap.GetBitmap(), aMask ); } } 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.GetAlpha().GetBitmap(), aMem, false, true); return css::uno::Sequence< sal_Int8 >( static_cast(aMem.GetData()), aMem.Tell() ); } else if ( aBmpEx.IsTransparent() ) { SvMemoryStream aMem; WriteDIB(aBmpEx.GetMask(), 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, long nY, const long nWidth, unsigned char* data, long nOff ) { bool bIsAlpha = false; long nX; int nAlpha; Scanline pReadScan; nOff += 3; switch( pAlphaReadAcc->GetScanlineFormat() ) { case ScanlineFormat::N8BitTcMask: pReadScan = pAlphaReadAcc->GetScanline( nY ); for( nX = 0; nX < nWidth; nX++ ) { nAlpha = data[ nOff ] = 255 - ( *pReadScan++ ); if( nAlpha != 255 ) bIsAlpha = true; nOff += 4; } break; case ScanlineFormat::N8BitPal: pReadScan = pAlphaReadAcc->GetScanline( nY ); for( nX = 0; nX < nWidth; nX++ ) { BitmapColor const& rColor( pAlphaReadAcc->GetPaletteColor(*pReadScan)); pReadScan++; nAlpha = data[ nOff ] = 255 - 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 ] = 255 - 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, long& rnWidth, long& rnHeight ) { AlphaMask aAlpha = aBmpEx.GetAlpha(); ::BitmapReadAccess* pBitmapReadAcc = aBitmap.AcquireReadAccess(); ::BitmapReadAccess* pAlphaReadAcc = nullptr; const long nWidth = rnWidth = pBitmapReadAcc->Width(); const long nHeight = rnHeight = pBitmapReadAcc->Height(); long nX, nY; bool bIsAlpha = false; if( aBmpEx.IsTransparent() || aBmpEx.IsAlpha() ) pAlphaReadAcc = aAlpha.AcquireReadAccess(); data = static_cast(malloc( nWidth*nHeight*4 )); long nOff = 0; ::Color aColor; unsigned int nAlpha = 255; vcl::bitmap::lookup_table premultiply_table = vcl::bitmap::get_premultiply_table(); for( nY = 0; nY < nHeight; nY++ ) { ::Scanline pReadScan; switch( pBitmapReadAcc->GetScanlineFormat() ) { case ScanlineFormat::N8BitPal: pReadScan = pBitmapReadAcc->GetScanline( nY ); if( pAlphaReadAcc ) if( readAlpha( pAlphaReadAcc, 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 data[ nOff++ ] = premultiply_table[nAlpha][aColor.GetRed()]; data[ nOff++ ] = premultiply_table[nAlpha][aColor.GetGreen()]; data[ nOff++ ] = premultiply_table[nAlpha][aColor.GetBlue()]; #else data[ nOff++ ] = premultiply_table[nAlpha][aColor.GetBlue()]; data[ nOff++ ] = premultiply_table[nAlpha][aColor.GetGreen()]; data[ nOff++ ] = premultiply_table[nAlpha][aColor.GetRed()]; nOff++; #endif } break; case ScanlineFormat::N24BitTcBgr: pReadScan = pBitmapReadAcc->GetScanline( nY ); if( pAlphaReadAcc ) if( readAlpha( pAlphaReadAcc, 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; data[ nOff + 3 ] = premultiply_table[nAlpha][*pReadScan++]; data[ nOff + 2 ] = premultiply_table[nAlpha][*pReadScan++]; data[ nOff + 1 ] = premultiply_table[nAlpha][*pReadScan++]; nOff += 4; #else if( pAlphaReadAcc ) nAlpha = data[ nOff + 3 ]; else nAlpha = data[ nOff + 3 ] = 255; data[ nOff++ ] = premultiply_table[nAlpha][*pReadScan++]; data[ nOff++ ] = premultiply_table[nAlpha][*pReadScan++]; data[ nOff++ ] = premultiply_table[nAlpha][*pReadScan++]; nOff++; #endif } break; case ScanlineFormat::N24BitTcRgb: pReadScan = pBitmapReadAcc->GetScanline( nY ); if( pAlphaReadAcc ) if( readAlpha( pAlphaReadAcc, 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; data[ nOff++ ] = premultiply_table[nAlpha][*pReadScan++]; data[ nOff++ ] = premultiply_table[nAlpha][*pReadScan++]; data[ nOff++ ] = premultiply_table[nAlpha][*pReadScan++]; #else if( pAlphaReadAcc ) nAlpha = data[ nOff + 3 ]; else nAlpha = data[ nOff + 3 ] = 255; data[ nOff++ ] = premultiply_table[nAlpha][pReadScan[ 2 ]]; data[ nOff++ ] = premultiply_table[nAlpha][pReadScan[ 1 ]]; data[ nOff++ ] = premultiply_table[nAlpha][pReadScan[ 0 ]]; pReadScan += 3; nOff++; #endif } break; case ScanlineFormat::N32BitTcBgra: pReadScan = pBitmapReadAcc->GetScanline( nY ); if( pAlphaReadAcc ) if( readAlpha( pAlphaReadAcc, 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; data[ nOff++ ] = premultiply_table[nAlpha][pReadScan[ 2 ]]; data[ nOff++ ] = premultiply_table[nAlpha][pReadScan[ 1 ]]; data[ nOff++ ] = premultiply_table[nAlpha][pReadScan[ 0 ]]; pReadScan += 4; #else if( pAlphaReadAcc ) nAlpha = data[ nOff + 3 ]; else nAlpha = data[ nOff + 3 ] = 255; data[ nOff++ ] = premultiply_table[nAlpha][*pReadScan++]; data[ nOff++ ] = premultiply_table[nAlpha][*pReadScan++]; data[ nOff++ ] = premultiply_table[nAlpha][*pReadScan++]; pReadScan++; nOff++; #endif } break; case ScanlineFormat::N32BitTcRgba: pReadScan = pBitmapReadAcc->GetScanline( nY ); if( pAlphaReadAcc ) if( readAlpha( pAlphaReadAcc, 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; data[ nOff++ ] = premultiply_table[nAlpha][*pReadScan++]; data[ nOff++ ] = premultiply_table[nAlpha][*pReadScan++]; data[ nOff++ ] = premultiply_table[nAlpha][*pReadScan++]; pReadScan++; #else if( pAlphaReadAcc ) nAlpha = data[ nOff + 3 ]; else nAlpha = data[ nOff + 3 ] = 255; data[ nOff++ ] = premultiply_table[nAlpha][pReadScan[ 2 ]]; data[ nOff++ ] = premultiply_table[nAlpha][pReadScan[ 1 ]]; data[ nOff++ ] = premultiply_table[nAlpha][pReadScan[ 0 ]]; pReadScan += 4; nOff++; #endif } break; default: SAL_INFO( "canvas.cairo", "fallback to GetColor - slow, format: " << static_cast(pBitmapReadAcc->GetScanlineFormat()) ); if( pAlphaReadAcc ) if( readAlpha( pAlphaReadAcc, 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; data[ nOff++ ] = premultiply_table[nAlpha][aColor.GetRed()]; data[ nOff++ ] = premultiply_table[nAlpha][aColor.GetGreen()]; data[ nOff++ ] = premultiply_table[nAlpha][aColor.GetBlue()]; #else if( pAlphaReadAcc ) nAlpha = data[ nOff + 3 ]; else nAlpha = data[ nOff + 3 ] = 255; data[ nOff++ ] = premultiply_table[nAlpha][aColor.GetBlue()]; data[ nOff++ ] = premultiply_table[nAlpha][aColor.GetGreen()]; data[ nOff++ ] = premultiply_table[nAlpha][aColor.GetRed()]; nOff ++; #endif } } } ::Bitmap::ReleaseAccess( pBitmapReadAcc ); if( pAlphaReadAcc ) aAlpha.ReleaseAccess( pAlphaReadAcc ); bHasAlpha = bIsAlpha; } uno::Sequence< sal_Int8 > CanvasExtractBitmapData(BitmapEx const & rBitmapEx, const geometry::IntegerRectangle2D& rect) { Bitmap aBitmap( rBitmapEx.GetBitmap() ); Bitmap aAlpha( rBitmapEx.GetAlpha().GetBitmap() ); Bitmap::ScopedReadAccess pReadAccess( aBitmap ); Bitmap::ScopedReadAccess pAlphaReadAccess( aAlpha.IsEmpty() ? nullptr : aAlpha.AcquireReadAccess(), 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( long y=rect.Y1; yGetScanline( y ); for( long x=rect.X1; xGetColor( y, x ).GetRed(); pRes[ nCurrPos++ ] = pReadAccess->GetColor( y, x ).GetGreen(); pRes[ nCurrPos++ ] = pReadAccess->GetColor( y, x ).GetBlue(); pRes[ nCurrPos++ ] = pAlphaReadAccess->GetIndexFromData( pScanlineReadAlpha, x ); } } else { for( 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), 1, &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.IsTransparent()) { Bitmap aBitmap(rBitmapEx.GetBitmap()); if(8 == aBitmap.GetSizePixel().Width() && 8 == aBitmap.GetSizePixel().Height()) { if(2 == aBitmap.GetColorCount()) { BitmapReadAccess* pRead = aBitmap.AcquireReadAccess(); if(pRead) { if(pRead->HasPalette() && 2 == pRead->GetPaletteEntryCount()) { const BitmapPalette& rPalette = pRead->GetPalette(); // #i123564# background and foreground were exchanged; of course // rPalette[0] is the background color o_rFront = rPalette[1]; o_rBack = rPalette[0]; bRet = true; } Bitmap::ReleaseAccess(pRead); } } } } return bRet; } 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; } lookup_table get_unpremultiply_table() { static bool inited; static sal_uInt8 unpremultiply_table[256][256]; if (!inited) { for (int a = 0; a < 256; ++a) for (int c = 0; c < 256; ++c) unpremultiply_table[a][c] = unpremultiply(c, a); inited = true; } return unpremultiply_table; } lookup_table get_premultiply_table() { static bool inited; static sal_uInt8 premultiply_table[256][256]; if (!inited) { for (int a = 0; a < 256; ++a) for (int c = 0; c < 256; ++c) premultiply_table[a][c] = premultiply(c, a); inited = true; } return premultiply_table; } bool convertBitmap32To24Plus8(BitmapEx const & rInput, BitmapEx & rResult) { Bitmap aBitmap(rInput.GetBitmap()); if (aBitmap.GetBitCount() != 32) return false; Size aSize = aBitmap.GetSizePixel(); Bitmap aResultBitmap(aSize, 24); AlphaMask aResultAlpha(aSize); { BitmapScopedWriteAccess pResultBitmapAccess(aResultBitmap); AlphaScopedWriteAccess pResultAlphaAccess(aResultAlpha); Bitmap::ScopedReadAccess pReadAccess(aBitmap); for (long nY = 0; nY < aSize.Height(); ++nY) { Scanline aResultScan = pResultBitmapAccess->GetScanline(nY); Scanline aResultScanAlpha = pResultAlphaAccess->GetScanline(nY); Scanline aReadScan = pReadAccess->GetScanline(nY); for (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.IsTransparent()) rResult = BitmapEx(aResultBitmap, rInput.GetAlpha()); else rResult = BitmapEx(aResultBitmap, aResultAlpha); return true; } } // end vcl::bitmap /* vim:set shiftwidth=4 softtabstop=4 expandtab: */