/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * This file incorporates work covered by the following license notice: * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed * with this work for additional information regarding copyright * ownership. The ASF licenses this file to you under the Apache * License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.apache.org/licenses/LICENSE-2.0 .
*/
namespace
{ /** * Perform a safe approximation of a polygon from double-precision * coordinates to integer coordinates, to ensure that it has at least 2 * pixels in both X and Y directions.
*/
tools::Polygon toPolygon( const basegfx::B2DPolygon& rPoly )
{
basegfx::B2DRange aRange = rPoly.getB2DRange(); double fW = aRange.getWidth(), fH = aRange.getHeight(); if (0.0 < fW && 0.0 < fH && (fW <= 1.0 || fH <= 1.0))
{ // This polygon not empty but is too small to display. Approximate it // with a rectangle large enough to be displayed. double nX = aRange.getMinX(), nY = aRange.getMinY(); double nW = std::max<double>(1.0, rtl::math::round(fW)); double nH = std::max<double>(1.0, rtl::math::round(fH));
// Caution: This method is nearly the same as // void OutputDevice::DrawPolyPolygon( const basegfx::B2DPolyPolygon& rB2DPolyPoly ) // so when changes are made here do not forget to make changes there, too
// AW: Do NOT paint empty PolyPolygons if(!rB2DPolyPoly.count()) return;
// we need a graphics if( !mpGraphics && !AcquireGraphics() ) return;
assert(mpGraphics);
if( mbInitClipRegion )
InitClipRegion();
if( mbOutputClipped ) return;
if( mbInitLineColor )
InitLineColor();
if( mbInitFillColor )
InitFillColor();
if (RasterOp::OverPaint == GetRasterOp())
{ // b2dpolygon support not implemented yet on non-UNX platforms
basegfx::B2DPolyPolygon aB2DPolyPolygon(rB2DPolyPoly);
// ensure it is closed if(!aB2DPolyPolygon.isClosed())
{ // maybe assert, prevents buffering due to making a copy
aB2DPolyPolygon.setClosed( true );
}
// create ObjectToDevice transformation const basegfx::B2DHomMatrix aFullTransform(ImplGetDeviceTransformation() * rObjectTransform); // TODO: this must not drop transparency for mpAlphaVDev case, but instead use premultiplied // alpha... but that requires using premultiplied alpha also for already drawn data constdouble fAdjustedTransparency = mpAlphaVDev ? 0 : fTransparency;
if (IsFillColor())
{
mpGraphics->DrawPolyPolygon(
aFullTransform,
aB2DPolyPolygon,
fAdjustedTransparency,
*this);
}
if (IsLineColor())
{ constbool bPixelSnapHairline(mnAntialiasing & AntialiasingFlags::PixelSnapHairline);
for(autoconst& rPolygon : std::as_const(aB2DPolyPolygon))
{
mpGraphics->DrawPolyLine(
aFullTransform,
rPolygon,
fAdjustedTransparency,
0.0, // tdf#124848 hairline
nullptr, // MM01
basegfx::B2DLineJoin::NONE,
css::drawing::LineCap_BUTT,
basegfx::deg2rad(15.0), // not used with B2DLineJoin::NONE, but the correct default
bPixelSnapHairline,
*this );
}
}
if( mpMetaFile )
{ // tdf#119843 need transformed Polygon here
basegfx::B2DPolyPolygon aB2DPolyPoly(rB2DPolyPoly);
aB2DPolyPoly.transform(rObjectTransform);
mpMetaFile->AddAction( new MetaTransparentAction(
tools::PolyPolygon(aB2DPolyPoly), static_cast< sal_uInt16 >(fTransparency * 100.0)));
}
if (mpAlphaVDev)
mpAlphaVDev->DrawTransparent(rObjectTransform, rB2DPolyPoly, fTransparency);
return;
}
// fallback to old polygon drawing if needed // tdf#119843 need transformed Polygon here
basegfx::B2DPolyPolygon aB2DPolyPoly(rB2DPolyPoly);
aB2DPolyPoly.transform(rObjectTransform);
DrawTransparent(
toPolyPolygon(aB2DPolyPoly), static_cast<sal_uInt16>(fTransparency * 100.0));
}
if (true #ifdefined UNX && ! defined MACOSX && ! defined IOS
&& GetBitCount() > 8 #endif #ifdef _WIN32 // workaround bad dithering on remote displaying when using GDI+ with toolbar button highlighting
&& !rPolyPoly.IsRect() #endif
)
{ // prepare the graphics device if( mbInitClipRegion )
InitClipRegion();
if( mbOutputClipped ) returntrue;
if( mbInitLineColor )
InitLineColor();
if( mbInitFillColor )
InitFillColor();
// get the polygon in device coordinates
basegfx::B2DPolyPolygon aB2DPolyPolygon(rPolyPoly.getB2DPolyPolygon()); const basegfx::B2DHomMatrix aTransform(ImplGetDeviceTransformation());
constdouble fTransparency = 0.01 * nTransparencePercent; if( mbFillColor )
{ // #i121591# // CAUTION: Only non printing (pixel-renderer) VCL commands from OutputDevices // should be used when printing. Normally this is avoided by the printer being // non-AAed and thus e.g. on WIN GdiPlus calls are not used. It may be necessary // to figure out a way of moving this code to its own function that is // overridden by the Print class, which will mean we deliberately override the // functionality and we use the fallback some lines below (which is not very good, // though. For now, WinSalGraphics::drawPolyPolygon will detect printer usage and // correct the wrong mapping (see there for details)
mpGraphics->DrawPolyPolygon(
aTransform,
aB2DPolyPolygon,
fTransparency,
*this);
bDrawn = true;
}
if( mbLineColor )
{ // disable the fill color for now
mpGraphics->SetFillColor();
// draw the border line constbool bPixelSnapHairline(mnAntialiasing & AntialiasingFlags::PixelSnapHairline);
for(autoconst& rPolygon : std::as_const(aB2DPolyPolygon))
{
bDrawn = mpGraphics->DrawPolyLine(
aTransform,
rPolygon,
fTransparency,
0.0, // tdf#124848 hairline
nullptr, // MM01
basegfx::B2DLineJoin::NONE,
css::drawing::LineCap_BUTT,
basegfx::deg2rad(15.0), // not used with B2DLineJoin::NONE, but the correct default
bPixelSnapHairline,
*this );
}
// prepare to restore the fill color
mbInitFillColor = mbFillColor;
}
}
// #i66849# Added fast path for exactly rectangular // polygons // #i83087# Naturally, system alpha blending cannot // work with separate alpha VDev if( !mpAlphaVDev && aPolyPoly.IsRect() )
{ // setup Graphics only here (other cases delegate // to basic OutDev methods) if ( mbInitClipRegion )
InitClipRegion();
if( !mbOutputClipped )
{
bDrawn = mpGraphics->DrawAlphaRect( aPixelRect.Left(), aPixelRect.Top(), // #i98405# use methods with small g, else one pixel too much will be painted. // This is because the source is a polygon which when painted would not paint // the rightmost and lowest pixel line(s), so use one pixel less for the // rectangle, too.
aPixelRect.getOpenWidth(), aPixelRect.getOpenHeight(),
sal::static_int_cast<sal_uInt8>(nTransparencePercent),
*this );
} else
{
bDrawn = true;
}
}
// short circuit for drawing an opaque polygon if( (nTransparencePercent < 1) || (mnDrawMode & DrawModeFlags::NoTransparency) )
{
DrawPolyPolygon( rPolyPoly ); return;
}
// short circuit for drawing an invisible polygon if( (!mbFillColor && !mbLineColor) || (nTransparencePercent >= 100) ) return; // tdf#84294: do not record it in metafile
if( mpMetaFile )
{ // missing here is to map the data using the DeviceTransformation
mpMetaFile->AddAction( new MetaFloatTransparentAction( rMtf, rPos, rSize, rTransparenceGradient ) );
}
if( xVDev->SetOutputSizePixel( aDstRect.GetSize(), true, true ) )
{ // tdf#150610 fix broken rendering of text meta actions // Even when drawing to a VirtualDevice that has antialiasing // disabled, text will still be drawn with some antialiased // pixels on HiDPI displays. So, use the antialiasing enabled // code to render if there are any text meta actions in the // metafile. if(GetAntialiasing() != AntialiasingFlags::NONE || rPos != rMtfPos || rSize != rMtfSize)
{ // #i102109# // For MetaFile replay (see task) it may now be necessary to take // into account that the content is AntiAlialiased and needs to be masked // like that. Instead of masking, i will use a copy-modify-paste cycle // here (as i already use in the VclPrimiziveRenderer with success)
xVDev->SetAntialiasing(GetAntialiasing());
// create MapMode for buffer (offset needed) and set
MapMode aMap(GetMapMode()); const Point aOutPos(PixelToLogic(aDstRect.TopLeft()));
aMap.SetOrigin(Point(-aOutPos.X(), -aOutPos.Y()));
xVDev->SetMapMode(aMap);
// copy MapMode state and disable for target constbool bOrigMapModeEnabled(IsMapModeEnabled());
EnableMapMode(false);
// copy MapMode state and disable for buffer constbool bBufferMapModeEnabled(xVDev->IsMapModeEnabled());
xVDev->EnableMapMode(false);
// copy content from original to buffer
xVDev->DrawOutDev( aPoint, xVDev->GetOutputSizePixel(), // dest
aDstRect.TopLeft(), xVDev->GetOutputSizePixel(), // source
*this);
// create alpha mask from gradient and get as Bitmap
xVDev->EnableMapMode(bBufferMapModeEnabled);
xVDev->SetDrawMode(DrawModeFlags::GrayGradient); // Related tdf#150610 draw gradient to VirtualDevice bounds // If we are here and the metafile bounds differs from the // VirtualDevice bounds so that we apply the transparency // gradient to any pixels drawn outside of the metafile // bounds.
xVDev->DrawGradient(tools::Rectangle(rPos, rSize), rTransparenceGradient);
xVDev->SetDrawMode(DrawModeFlags::Default);
xVDev->EnableMapMode(false);
AlphaMask aAlpha(xVDev->GetBitmap(aPoint, xVDev->GetOutputSizePixel())); const AlphaMask& aPaintAlpha(aPaint.GetAlphaMask()); // The alpha mask is inverted from what // is expected so invert it again
aAlpha.Invert(); // convert to alpha
aAlpha.BlendWith(aPaintAlpha);
xVDev.disposeAndClear();
// draw masked content to target and restore MapMode
DrawBitmapEx(aDstRect.TopLeft(), BitmapEx(aPaint.GetBitmap(), aAlpha));
EnableMapMode(bOrigMapModeEnabled);
} else
{
MapMode aMap( GetMapMode() );
Point aOutPos( PixelToLogic( aDstRect.TopLeft() ) ); constbool bOldMap = mbMap;
AlphaMask aAlpha(xVDev->GetBitmap(Point(), xVDev->GetOutputSizePixel())); const AlphaMask& aPaintAlpha(aPaint.GetAlphaMask()); // The alpha mask is inverted from what // is expected so invert it again
aAlpha.Invert(); // convert to alpha
aAlpha.BlendWith(aPaintAlpha);
/** Determines whether the action can handle transparency correctly (i.e. when painted on white background, does the action still look correct)?
*/ bool DoesActionHandleTransparency( const MetaAction& rAct )
{ // MetaActionType::FLOATTRANSPARENT can contain a whole metafile, // which is to be rendered with the given transparent gradient. We // currently cannot emulate transparent painting on a white // background reliably.
// the remainder can handle printing itself correctly on a uniform // white background. switch( rAct.GetType() )
{ case MetaActionType::Transparent: case MetaActionType::BMPEX: case MetaActionType::BMPEXSCALE: case MetaActionType::BMPEXSCALEPART: returntrue;
default: returnfalse;
}
}
bool doesRectCoverWithUniformColor(
tools::Rectangle const & rPrevRect,
tools::Rectangle const & rCurrRect,
OutputDevice const & rMapModeVDev)
{ // shape needs to fully cover previous content, and have uniform // color return (rMapModeVDev.LogicToPixel(rCurrRect).Contains(rPrevRect) &&
rMapModeVDev.IsFillColor());
}
/** #107169# Convert BitmapEx to Bitmap with appropriately blended color. Convert MetaTransparentAction to plain polygon, appropriately colored
@param o_rMtf Add converted actions to this metafile
*/ void ImplConvertTransparentAction( GDIMetaFile& o_rMtf, const MetaAction& rAct, const OutputDevice& rStateOutDev,
Color aBgColor )
{ if (rAct.GetType() == MetaActionType::Transparent)
{ const MetaTransparentAction* pTransAct = static_cast<const MetaTransparentAction*>(&rAct);
sal_uInt16 nTransparency( pTransAct->GetTransparence() );
// #i10613# Respect transparency for draw color if (nTransparency)
{
o_rMtf.AddAction(new MetaPushAction(vcl::PushFlags::LINECOLOR|vcl::PushFlags::FILLCOLOR));
case MetaActionType::PIXEL: case MetaActionType::BMP: case MetaActionType::BMPSCALE: case MetaActionType::BMPSCALEPART: case MetaActionType::BMPEX: case MetaActionType::BMPEXSCALE: case MetaActionType::BMPEXSCALEPART: case MetaActionType::MASK: case MetaActionType::MASKSCALE: case MetaActionType::MASKSCALEPART: case MetaActionType::GRADIENT: case MetaActionType::GRADIENTEX: case MetaActionType::HATCH: case MetaActionType::WALLPAPER: case MetaActionType::Transparent: case MetaActionType::FLOATTRANSPARENT: case MetaActionType::EPS: case MetaActionType::TEXTRECT: case MetaActionType::STRETCHTEXT: case MetaActionType::TEXTLINE: // all other actions: generate non-transparent output
bRet = true; break;
if (!aString.isEmpty())
{ const Point aPtLog( rTextAct.GetPoint() );
// #105987# Use API method instead of Impl* methods // #107490# Set base parameter equal to index parameter
rOut.GetTextBoundRect( aActionBounds, rTextAct.GetText(), rTextAct.GetIndex(),
rTextAct.GetIndex(), rTextAct.GetLen() );
aActionBounds.Move( aPtLog.X(), aPtLog.Y() );
}
} break;
// TODO: this massive function operates on metafiles, so eventually it should probably // be shifted to the GDIMetaFile class bool OutputDevice::RemoveTransparenciesFromMetaFile( const GDIMetaFile& rInMtf, GDIMetaFile& rOutMtf,
tools::Long nMaxBmpDPIX, tools::Long nMaxBmpDPIY, bool bReduceTransparency, bool bTransparencyAutoMode, bool bDownsampleBitmaps, const Color& rBackground
)
{
MetaAction* pCurrAct; bool bTransparent( false );
rOutMtf.Clear();
#ifdef MACOSX // tdf#164354 skip unnecessary transparency removal // On macOS, there are no known problems drawing semi-transparent // shapes, fill, text, and bitmaps so only do this sometimes very // expensive step when both reduce transparency and no // transparency are true. if(bReduceTransparency && !bTransparencyAutoMode) #else if(!bReduceTransparency || bTransparencyAutoMode) #endif
bTransparent = rInMtf.HasTransparentActions();
// #i10613# Determine set of connected components containing transparent objects. These are // then processed as bitmaps, the original actions are removed from the metafile. if( !bTransparent )
{ // nothing transparent -> just copy
rOutMtf = rInMtf;
} else
{ // #i10613# // This works as follows: we want a number of distinct sets of // connected components, where each set contains metafile // actions that are intersecting (note: there are possibly // more actions contained as are directly intersecting, // because we can only produce rectangular bitmaps later // on. Thus, each set of connected components is the smallest // enclosing, axis-aligned rectangle that completely bounds a // number of intersecting metafile actions, plus any action // that would otherwise be cut in two). Therefore, we // iteratively add metafile actions from the original metafile // to this connected components list (aCCList), by checking // each element's bounding box against intersection with the // metaaction at hand. // All those intersecting elements are removed from aCCList // and collected in a temporary list (aCCMergeList). After all // elements have been checked, the aCCMergeList elements are // merged with the metaaction at hand into one resulting // connected component, with one big bounding box, and // inserted into aCCList again. // The time complexity of this algorithm is O(n^3), where n is // the number of metafile actions, and it finds all distinct // regions of rectangle-bounded connected components. This // algorithm was designed by AF.
// STAGE 1: Detect background
// Receives uniform background content, and is _not_ merged // nor checked for intersection against other aCCList elements
ConnectedComponents aBackgroundComponent;
// Read the configuration value of minimal object area where transparency will be removed double fReduceTransparencyMinArea = officecfg::Office::Common::VCL::ReduceTransparencyMinArea::get() / 100.0;
SAL_WARN_IF(fReduceTransparencyMinArea > 1.0, "vcl", "Value of ReduceTransparencyMinArea config option is too high");
SAL_WARN_IF(fReduceTransparencyMinArea < 0.0, "vcl", "Value of ReduceTransparencyMinArea config option is too low");
fReduceTransparencyMinArea = std::clamp(fReduceTransparencyMinArea, 0.0, 1.0);
// create an OutputDevice to record mapmode changes and the like
ScopedVclPtrInstance< VirtualDevice > aMapModeVDev;
aMapModeVDev->mnDPIX = mnDPIX;
aMapModeVDev->mnDPIY = mnDPIY;
aMapModeVDev->EnableOutput(false);
// weed out page-filling background objects (if they are // uniformly coloured). Keeping them outside the other // connected components often prevents whole-page bitmap // generation. bool bStillBackground=true; // true until first non-bg action int nActionNum = 0, nLastBgAction = -1;
pCurrAct=const_cast<GDIMetaFile&>(rInMtf).FirstAction(); if( rBackground != COL_TRANSPARENT )
{
aBackgroundComponent.aBgColor = rBackground;
aBackgroundComponent.aBounds = GetBackgroundComponentBounds();
} while( pCurrAct && bStillBackground )
{ switch( pCurrAct->GetType() )
{ case MetaActionType::RECT:
{ if( !checkRect(
aBackgroundComponent.aBounds,
aBackgroundComponent.aBgColor, static_cast<const MetaRectAction*>(pCurrAct)->GetRect(),
*aMapModeVDev) )
bStillBackground=false; // incomplete occlusion of background else
nLastBgAction=nActionNum; // this _is_ background break;
} case MetaActionType::POLYGON:
{ const tools::Polygon aPoly( static_cast<const MetaPolygonAction*>(pCurrAct)->GetPolygon()); if( !basegfx::utils::isRectangle(
aPoly.getB2DPolygon()) ||
!checkRect(
aBackgroundComponent.aBounds,
aBackgroundComponent.aBgColor,
aPoly.GetBoundRect(),
*aMapModeVDev) )
bStillBackground=false; // incomplete occlusion of background else
nLastBgAction=nActionNum; // this _is_ background break;
} case MetaActionType::POLYPOLYGON:
{ const tools::PolyPolygon aPoly( static_cast<const MetaPolyPolygonAction*>(pCurrAct)->GetPolyPolygon()); if( aPoly.Count() != 1 ||
!basegfx::utils::isRectangle(
aPoly[0].getB2DPolygon()) ||
!checkRect(
aBackgroundComponent.aBounds,
aBackgroundComponent.aBgColor,
aPoly.GetBoundRect(),
*aMapModeVDev) )
bStillBackground=false; // incomplete occlusion of background else
nLastBgAction=nActionNum; // this _is_ background break;
} case MetaActionType::WALLPAPER:
{ if( !checkRect(
aBackgroundComponent.aBounds,
aBackgroundComponent.aBgColor, static_cast<const MetaWallpaperAction*>(pCurrAct)->GetRect(),
*aMapModeVDev) )
bStillBackground=false; // incomplete occlusion of background else
nLastBgAction=nActionNum; // this _is_ background break;
} default:
{ if( ImplIsNotTransparent( *pCurrAct,
*aMapModeVDev ) )
bStillBackground=false; // non-transparent action, possibly // not uniform else // extend current bounds (next uniform action // needs to fully cover this area)
aBackgroundComponent.aBounds.Union(
ImplCalcActionBounds(*pCurrAct, *aMapModeVDev) ); break;
}
}
// execute action to get correct MapModes etc.
pCurrAct->Execute( aMapModeVDev.get() );
if (nLastBgAction != -1)
{
size_t nActionSize = rInMtf.GetActionSize(); // tdf#134736 move nLastBgAction to also include any trailing pops for (size_t nPostLastBgAction = nLastBgAction + 1; nPostLastBgAction < nActionSize; ++nPostLastBgAction)
{ if (rInMtf.GetAction(nPostLastBgAction)->GetType() != MetaActionType::POP) break;
nLastBgAction = nPostLastBgAction;
}
}
aMapModeVDev->ClearStack(); // clean up aMapModeVDev
// fast-forward until one after the last background action // (need to reconstruct map mode vdev state)
nActionNum=0;
pCurrAct=const_cast<GDIMetaFile&>(rInMtf).FirstAction(); while( pCurrAct && nActionNum<=nLastBgAction )
{ // up to and including last ink-generating background // action go to background component
aBackgroundComponent.aComponentList.emplace_back(
pCurrAct, nActionNum );
// execute action to get correct MapModes etc.
pCurrAct->Execute( aMapModeVDev.get() );
pCurrAct=const_cast<GDIMetaFile&>(rInMtf).NextAction();
++nActionNum;
}
// STAGE 2: Generate connected components list
::std::vector<ConnectedComponents> aCCList; // contains distinct sets of connected components as elements.
// iterate over all actions (start where background action // search left off) for( ;
pCurrAct;
pCurrAct=const_cast<GDIMetaFile&>(rInMtf).NextAction(), ++nActionNum )
{ // execute action to get correct MapModes etc.
pCurrAct->Execute( aMapModeVDev.get() );
// cache bounds of current action const tools::Rectangle aBBCurrAct( ImplCalcActionBounds(*pCurrAct, *aMapModeVDev) );
// accumulate collected bounds here, initialize with current action
tools::Rectangle aTotalBounds( aBBCurrAct ); // thus, aTotalComponents.aBounds is empty // for non-output-generating actions bool bTreatSpecial( false );
ConnectedComponents aTotalComponents;
// STAGE 2.1: Search for intersecting cc entries
// if aBBCurrAct is empty, it will intersect with no // aCCList member. Thus, we can save the check. // Furthermore, this ensures that non-output-generating // actions get their own aCCList entry, which is necessary // when copying them to the output metafile (see stage 4 // below).
// #107169# Wholly transparent objects need // not be considered for connected components, // too. Just put each of them into a separate // component.
aTotalComponents.bIsFullyTransparent = !ImplIsNotTransparent(*pCurrAct, *aMapModeVDev);
if( !aBBCurrAct.IsEmpty() &&
!aTotalComponents.bIsFullyTransparent )
{ if( !aBackgroundComponent.aComponentList.empty() &&
!aBackgroundComponent.aBounds.Contains(aTotalBounds) )
{ // it seems the background is not large enough. to // be on the safe side, combine with this component.
aTotalBounds.Union( aBackgroundComponent.aBounds );
// extract all aCurr actions to aTotalComponents
aTotalComponents.aComponentList.splice( aTotalComponents.aComponentList.end(),
aBackgroundComponent.aComponentList );
// now, this is unfortunate: since changing anyone of // the aCCList elements (e.g. by merging or addition // of an action) might generate new intersection with // other aCCList elements, have to repeat the whole // element scanning, until nothing changes anymore. // Thus, this loop here makes us O(n^3) in the worst // case. do
{ // only loop here if 'intersects' branch below was hit
bSomeComponentsChanged = false;
// iterate over all current members of aCCList for( auto aCurrCC=aCCList.begin(); aCurrCC != aCCList.end(); )
{
--> --------------------
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.