diff options
Diffstat (limited to 'vcl/source/gdi/print2.cxx')
-rw-r--r-- | vcl/source/gdi/print2.cxx | 1593 |
1 files changed, 1593 insertions, 0 deletions
diff --git a/vcl/source/gdi/print2.cxx b/vcl/source/gdi/print2.cxx new file mode 100644 index 000000000000..25ba80003fd7 --- /dev/null +++ b/vcl/source/gdi/print2.cxx @@ -0,0 +1,1593 @@ +/************************************************************************* + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * Copyright 2000, 2010 Oracle and/or its affiliates. + * + * OpenOffice.org - a multi-platform office productivity suite + * + * This file is part of OpenOffice.org. + * + * OpenOffice.org is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 3 + * only, as published by the Free Software Foundation. + * + * OpenOffice.org is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License version 3 for more details + * (a copy is included in the LICENSE file that accompanied this code). + * + * You should have received a copy of the GNU Lesser General Public License + * version 3 along with OpenOffice.org. If not, see + * <http://www.openoffice.org/license.html> + * for a copy of the LGPLv3 License. + * + ************************************************************************/ + +// MARKER(update_precomp.py): autogen include statement, do not remove +#include "precompiled_vcl.hxx" + +#include <functional> +#include <algorithm> +#include <utility> +#include <list> +#include <vector> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <tools/debug.hxx> +#include <vcl/virdev.hxx> +#include <vcl/metaact.hxx> +#include <vcl/gdimtf.hxx> +#include <vcl/print.h> +#include <vcl/salbtype.hxx> +#include <vcl/print.hxx> +#include <vcl/svapp.hxx> +#include <vcl/sallayout.hxx> +#include <vcl/bmpacc.hxx> + +#include "pdfwriter_impl.hxx" + +// ----------- +// - Defines - +// ----------- + +#define MAX_TILE_WIDTH 1024 +#define MAX_TILE_HEIGHT 1024 + +// --------- +// - Types - +// --------- + +typedef ::std::pair< MetaAction*, int > Component; // MetaAction plus index in metafile + +typedef ::std::list< Component > ComponentList; + +// List of (intersecting) actions, plus overall bounds +struct ConnectedComponents +{ + ConnectedComponents() : + aComponentList(), + aBounds(), + aBgColor(COL_WHITE), + bIsSpecial(false), + bIsFullyTransparent(false) + {} + + ComponentList aComponentList; + Rectangle aBounds; + Color aBgColor; + bool bIsSpecial; + bool bIsFullyTransparent; +}; + +typedef ::std::list< ConnectedComponents > ConnectedComponentsList; + + +// ----------- +// - Printer - +// ----------- + +/** #i10613# Extracted from Printer::GetPreparedMetaFile. Returns true + if given action requires special handling (usually because of + transparency) +*/ +static bool ImplIsActionSpecial( const MetaAction& rAct ) +{ + switch( rAct.GetType() ) + { + case META_TRANSPARENT_ACTION: + return true; + + case META_FLOATTRANSPARENT_ACTION: + return true; + + case META_BMPEX_ACTION: + return static_cast<const MetaBmpExAction&>(rAct).GetBitmapEx().IsTransparent(); + + case META_BMPEXSCALE_ACTION: + return static_cast<const MetaBmpExScaleAction&>(rAct).GetBitmapEx().IsTransparent(); + + case META_BMPEXSCALEPART_ACTION: + return static_cast<const MetaBmpExScalePartAction&>(rAct).GetBitmapEx().IsTransparent(); + + default: + return false; + } +} + +/** Check whether rCurrRect rectangle fully covers io_rPrevRect - if + yes, return true and update o_rBgColor + */ +static bool checkRect( Rectangle& io_rPrevRect, + Color& o_rBgColor, + const Rectangle& rCurrRect, + OutputDevice& rMapModeVDev ) +{ + // shape needs to fully cover previous content, and have uniform + // color + const bool bRet( + rMapModeVDev.LogicToPixel(rCurrRect).IsInside(io_rPrevRect) && + rMapModeVDev.IsFillColor() ); + + if( bRet ) + { + io_rPrevRect = rCurrRect; + o_rBgColor = rMapModeVDev.GetFillColor(); + } + + return bRet; +} + +/** #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 +*/ +static void ImplConvertTransparentAction( GDIMetaFile& o_rMtf, + const MetaAction& rAct, + const OutputDevice& rStateOutDev, + Color aBgColor ) +{ + if( rAct.GetType() == META_TRANSPARENT_ACTION ) + { + const MetaTransparentAction* pTransAct = static_cast<const MetaTransparentAction*>(&rAct); + USHORT nTransparency( pTransAct->GetTransparence() ); + + // #i10613# Respect transparency for draw color + if( nTransparency ) + { + o_rMtf.AddAction( new MetaPushAction( PUSH_LINECOLOR|PUSH_FILLCOLOR ) ); + + // assume white background for alpha blending + Color aLineColor( rStateOutDev.GetLineColor() ); + aLineColor.SetRed( static_cast<UINT8>( (255L*nTransparency + (100L - nTransparency)*aLineColor.GetRed()) / 100L ) ); + aLineColor.SetGreen( static_cast<UINT8>( (255L*nTransparency + (100L - nTransparency)*aLineColor.GetGreen()) / 100L ) ); + aLineColor.SetBlue( static_cast<UINT8>( (255L*nTransparency + (100L - nTransparency)*aLineColor.GetBlue()) / 100L ) ); + o_rMtf.AddAction( new MetaLineColorAction(aLineColor, TRUE) ); + + Color aFillColor( rStateOutDev.GetFillColor() ); + aFillColor.SetRed( static_cast<UINT8>( (255L*nTransparency + (100L - nTransparency)*aFillColor.GetRed()) / 100L ) ); + aFillColor.SetGreen( static_cast<UINT8>( (255L*nTransparency + (100L - nTransparency)*aFillColor.GetGreen()) / 100L ) ); + aFillColor.SetBlue( static_cast<UINT8>( (255L*nTransparency + (100L - nTransparency)*aFillColor.GetBlue()) / 100L ) ); + o_rMtf.AddAction( new MetaFillColorAction(aFillColor, TRUE) ); + } + + o_rMtf.AddAction( new MetaPolyPolygonAction(pTransAct->GetPolyPolygon()) ); + + if( nTransparency ) + o_rMtf.AddAction( new MetaPopAction() ); + } + else + { + BitmapEx aBmpEx; + + switch( rAct.GetType() ) + { + case META_BMPEX_ACTION: + aBmpEx = static_cast<const MetaBmpExAction&>(rAct).GetBitmapEx(); + break; + + case META_BMPEXSCALE_ACTION: + aBmpEx = static_cast<const MetaBmpExScaleAction&>(rAct).GetBitmapEx(); + break; + + case META_BMPEXSCALEPART_ACTION: + aBmpEx = static_cast<const MetaBmpExScaleAction&>(rAct).GetBitmapEx(); + break; + + case META_TRANSPARENT_ACTION: + + default: + DBG_ERROR("Printer::GetPreparedMetafile impossible state reached"); + break; + } + + Bitmap aBmp( aBmpEx.GetBitmap() ); + if( !aBmpEx.IsAlpha() ) + { + // blend with mask + BitmapReadAccess* pRA = aBmp.AcquireReadAccess(); + + if( !pRA ) + return; // what else should I do? + + Color aActualColor( aBgColor ); + + if( pRA->HasPalette() ) + aActualColor = pRA->GetBestPaletteColor( aBgColor ).operator Color(); + + aBmp.ReleaseAccess(pRA); + + // did we get true white? + if( aActualColor.GetColorError( aBgColor ) ) + { + // no, create truecolor bitmap, then + aBmp.Convert( BMP_CONVERSION_24BIT ); + + // fill masked out areas white + aBmp.Replace( aBmpEx.GetMask(), aBgColor ); + } + else + { + // fill masked out areas white + aBmp.Replace( aBmpEx.GetMask(), aActualColor ); + } + } + else + { + // blend with alpha channel + aBmp.Convert( BMP_CONVERSION_24BIT ); + aBmp.Blend(aBmpEx.GetAlpha(),aBgColor); + } + + // add corresponding action + switch( rAct.GetType() ) + { + case META_BMPEX_ACTION: + o_rMtf.AddAction( new MetaBmpAction( + static_cast<const MetaBmpExAction&>(rAct).GetPoint(), + aBmp )); + break; + case META_BMPEXSCALE_ACTION: + o_rMtf.AddAction( new MetaBmpScaleAction( + static_cast<const MetaBmpExScaleAction&>(rAct).GetPoint(), + static_cast<const MetaBmpExScaleAction&>(rAct).GetSize(), + aBmp )); + break; + case META_BMPEXSCALEPART_ACTION: + o_rMtf.AddAction( new MetaBmpScalePartAction( + static_cast<const MetaBmpExScalePartAction&>(rAct).GetDestPoint(), + static_cast<const MetaBmpExScalePartAction&>(rAct).GetDestSize(), + static_cast<const MetaBmpExScalePartAction&>(rAct).GetSrcPoint(), + static_cast<const MetaBmpExScalePartAction&>(rAct).GetSrcSize(), + aBmp )); + break; + default: + DBG_ERROR("Unexpected case"); + break; + } + } +} + +// #i10613# Extracted from ImplCheckRect::ImplCreate +// Returns true, if given action creates visible (i.e. non-transparent) output +static bool ImplIsNotTransparent( const MetaAction& rAct, const OutputDevice& rOut ) +{ + const bool bLineTransparency( rOut.IsLineColor() ? rOut.GetLineColor().GetTransparency() == 255 : true ); + const bool bFillTransparency( rOut.IsFillColor() ? rOut.GetFillColor().GetTransparency() == 255 : true ); + bool bRet( false ); + + switch( rAct.GetType() ) + { + case META_POINT_ACTION: + if( !bLineTransparency ) + bRet = true; + break; + + case META_LINE_ACTION: + if( !bLineTransparency ) + bRet = true; + break; + + case META_RECT_ACTION: + if( !bLineTransparency || !bFillTransparency ) + bRet = true; + break; + + case META_ROUNDRECT_ACTION: + if( !bLineTransparency || !bFillTransparency ) + bRet = true; + break; + + case META_ELLIPSE_ACTION: + if( !bLineTransparency || !bFillTransparency ) + bRet = true; + break; + + case META_ARC_ACTION: + if( !bLineTransparency || !bFillTransparency ) + bRet = true; + break; + + case META_PIE_ACTION: + if( !bLineTransparency || !bFillTransparency ) + bRet = true; + break; + + case META_CHORD_ACTION: + if( !bLineTransparency || !bFillTransparency ) + bRet = true; + break; + + case META_POLYLINE_ACTION: + if( !bLineTransparency ) + bRet = true; + break; + + case META_POLYGON_ACTION: + if( !bLineTransparency || !bFillTransparency ) + bRet = true; + break; + + case META_POLYPOLYGON_ACTION: + if( !bLineTransparency || !bFillTransparency ) + bRet = true; + break; + + case META_TEXT_ACTION: + { + const MetaTextAction& rTextAct = static_cast<const MetaTextAction&>(rAct); + const XubString aString( rTextAct.GetText(), rTextAct.GetIndex(), rTextAct.GetLen() ); + + if( aString.Len() ) + bRet = true; + } + break; + + case META_TEXTARRAY_ACTION: + { + const MetaTextArrayAction& rTextAct = static_cast<const MetaTextArrayAction&>(rAct); + const XubString aString( rTextAct.GetText(), rTextAct.GetIndex(), rTextAct.GetLen() ); + + if( aString.Len() ) + bRet = true; + } + break; + + case META_PIXEL_ACTION: + case META_BMP_ACTION: + case META_BMPSCALE_ACTION: + case META_BMPSCALEPART_ACTION: + case META_BMPEX_ACTION: + case META_BMPEXSCALE_ACTION: + case META_BMPEXSCALEPART_ACTION: + case META_MASK_ACTION: + case META_MASKSCALE_ACTION: + case META_MASKSCALEPART_ACTION: + case META_GRADIENT_ACTION: + case META_GRADIENTEX_ACTION: + case META_HATCH_ACTION: + case META_WALLPAPER_ACTION: + case META_TRANSPARENT_ACTION: + case META_FLOATTRANSPARENT_ACTION: + case META_EPS_ACTION: + case META_TEXTRECT_ACTION: + case META_STRETCHTEXT_ACTION: + case META_TEXTLINE_ACTION: + // all other actions: generate non-transparent output + bRet = true; + break; + + default: + break; + } + + return bRet; +} + +// #i10613# Extracted from ImplCheckRect::ImplCreate +static Rectangle ImplCalcActionBounds( const MetaAction& rAct, const OutputDevice& rOut ) +{ + Rectangle aActionBounds; + + switch( rAct.GetType() ) + { + case META_PIXEL_ACTION: + aActionBounds = Rectangle( static_cast<const MetaPixelAction&>(rAct).GetPoint(), Size( 1, 1 ) ); + break; + + case META_POINT_ACTION: + aActionBounds = Rectangle( static_cast<const MetaPointAction&>(rAct).GetPoint(), Size( 1, 1 ) ); + break; + + case META_LINE_ACTION: + { + const MetaLineAction& rMetaLineAction = static_cast<const MetaLineAction&>(rAct); + aActionBounds = Rectangle( rMetaLineAction.GetStartPoint(), rMetaLineAction.GetEndPoint() ); + const long nLineWidth(rMetaLineAction.GetLineInfo().GetWidth()); + if(nLineWidth) + { + const long nHalfLineWidth((nLineWidth + 1) / 2); + aActionBounds.Left() -= nHalfLineWidth; + aActionBounds.Top() -= nHalfLineWidth; + aActionBounds.Right() += nHalfLineWidth; + aActionBounds.Bottom() += nHalfLineWidth; + } + break; + } + + case META_RECT_ACTION: + aActionBounds = static_cast<const MetaRectAction&>(rAct).GetRect(); + break; + + case META_ROUNDRECT_ACTION: + aActionBounds = Polygon( static_cast<const MetaRoundRectAction&>(rAct).GetRect(), + static_cast<const MetaRoundRectAction&>(rAct).GetHorzRound(), + static_cast<const MetaRoundRectAction&>(rAct).GetVertRound() ).GetBoundRect(); + break; + + case META_ELLIPSE_ACTION: + { + const Rectangle& rRect = static_cast<const MetaEllipseAction&>(rAct).GetRect(); + aActionBounds = Polygon( rRect.Center(), + rRect.GetWidth() >> 1, + rRect.GetHeight() >> 1 ).GetBoundRect(); + break; + } + + case META_ARC_ACTION: + aActionBounds = Polygon( static_cast<const MetaArcAction&>(rAct).GetRect(), + static_cast<const MetaArcAction&>(rAct).GetStartPoint(), + static_cast<const MetaArcAction&>(rAct).GetEndPoint(), POLY_ARC ).GetBoundRect(); + break; + + case META_PIE_ACTION: + aActionBounds = Polygon( static_cast<const MetaPieAction&>(rAct).GetRect(), + static_cast<const MetaPieAction&>(rAct).GetStartPoint(), + static_cast<const MetaPieAction&>(rAct).GetEndPoint(), POLY_PIE ).GetBoundRect(); + break; + + case META_CHORD_ACTION: + aActionBounds = Polygon( static_cast<const MetaChordAction&>(rAct).GetRect(), + static_cast<const MetaChordAction&>(rAct).GetStartPoint(), + static_cast<const MetaChordAction&>(rAct).GetEndPoint(), POLY_CHORD ).GetBoundRect(); + break; + + case META_POLYLINE_ACTION: + { + const MetaPolyLineAction& rMetaPolyLineAction = static_cast<const MetaPolyLineAction&>(rAct); + aActionBounds = rMetaPolyLineAction.GetPolygon().GetBoundRect(); + const long nLineWidth(rMetaPolyLineAction.GetLineInfo().GetWidth()); + if(nLineWidth) + { + const long nHalfLineWidth((nLineWidth + 1) / 2); + aActionBounds.Left() -= nHalfLineWidth; + aActionBounds.Top() -= nHalfLineWidth; + aActionBounds.Right() += nHalfLineWidth; + aActionBounds.Bottom() += nHalfLineWidth; + } + break; + } + + case META_POLYGON_ACTION: + aActionBounds = static_cast<const MetaPolygonAction&>(rAct).GetPolygon().GetBoundRect(); + break; + + case META_POLYPOLYGON_ACTION: + aActionBounds = static_cast<const MetaPolyPolygonAction&>(rAct).GetPolyPolygon().GetBoundRect(); + break; + + case META_BMP_ACTION: + aActionBounds = Rectangle( static_cast<const MetaBmpAction&>(rAct).GetPoint(), + rOut.PixelToLogic( static_cast<const MetaBmpAction&>(rAct).GetBitmap().GetSizePixel() ) ); + break; + + case META_BMPSCALE_ACTION: + aActionBounds = Rectangle( static_cast<const MetaBmpScaleAction&>(rAct).GetPoint(), + static_cast<const MetaBmpScaleAction&>(rAct).GetSize() ); + break; + + case META_BMPSCALEPART_ACTION: + aActionBounds = Rectangle( static_cast<const MetaBmpScalePartAction&>(rAct).GetDestPoint(), + static_cast<const MetaBmpScalePartAction&>(rAct).GetDestSize() ); + break; + + case META_BMPEX_ACTION: + aActionBounds = Rectangle( static_cast<const MetaBmpExAction&>(rAct).GetPoint(), + rOut.PixelToLogic( static_cast<const MetaBmpExAction&>(rAct).GetBitmapEx().GetSizePixel() ) ); + break; + + case META_BMPEXSCALE_ACTION: + aActionBounds = Rectangle( static_cast<const MetaBmpExScaleAction&>(rAct).GetPoint(), + static_cast<const MetaBmpExScaleAction&>(rAct).GetSize() ); + break; + + case META_BMPEXSCALEPART_ACTION: + aActionBounds = Rectangle( static_cast<const MetaBmpExScalePartAction&>(rAct).GetDestPoint(), + static_cast<const MetaBmpExScalePartAction&>(rAct).GetDestSize() ); + break; + + case META_MASK_ACTION: + aActionBounds = Rectangle( static_cast<const MetaMaskAction&>(rAct).GetPoint(), + rOut.PixelToLogic( static_cast<const MetaMaskAction&>(rAct).GetBitmap().GetSizePixel() ) ); + break; + + case META_MASKSCALE_ACTION: + aActionBounds = Rectangle( static_cast<const MetaMaskScaleAction&>(rAct).GetPoint(), + static_cast<const MetaMaskScaleAction&>(rAct).GetSize() ); + break; + + case META_MASKSCALEPART_ACTION: + aActionBounds = Rectangle( static_cast<const MetaMaskScalePartAction&>(rAct).GetDestPoint(), + static_cast<const MetaMaskScalePartAction&>(rAct).GetDestSize() ); + break; + + case META_GRADIENT_ACTION: + aActionBounds = static_cast<const MetaGradientAction&>(rAct).GetRect(); + break; + + case META_GRADIENTEX_ACTION: + aActionBounds = static_cast<const MetaGradientExAction&>(rAct).GetPolyPolygon().GetBoundRect(); + break; + + case META_HATCH_ACTION: + aActionBounds = static_cast<const MetaHatchAction&>(rAct).GetPolyPolygon().GetBoundRect(); + break; + + case META_WALLPAPER_ACTION: + aActionBounds = static_cast<const MetaWallpaperAction&>(rAct).GetRect(); + break; + + case META_TRANSPARENT_ACTION: + aActionBounds = static_cast<const MetaTransparentAction&>(rAct).GetPolyPolygon().GetBoundRect(); + break; + + case META_FLOATTRANSPARENT_ACTION: + aActionBounds = Rectangle( static_cast<const MetaFloatTransparentAction&>(rAct).GetPoint(), + static_cast<const MetaFloatTransparentAction&>(rAct).GetSize() ); + break; + + case META_EPS_ACTION: + aActionBounds = Rectangle( static_cast<const MetaEPSAction&>(rAct).GetPoint(), + static_cast<const MetaEPSAction&>(rAct).GetSize() ); + break; + + case META_TEXT_ACTION: + { + const MetaTextAction& rTextAct = static_cast<const MetaTextAction&>(rAct); + const XubString aString( rTextAct.GetText(), rTextAct.GetIndex(), rTextAct.GetLen() ); + + if( aString.Len() ) + { + 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; + + case META_TEXTARRAY_ACTION: + { + const MetaTextArrayAction& rTextAct = static_cast<const MetaTextArrayAction&>(rAct); + const XubString aString( rTextAct.GetText(), rTextAct.GetIndex(), rTextAct.GetLen() ); + const long nLen = aString.Len(); + + if( nLen ) + { + // #105987# ImplLayout takes everything in logical coordinates + SalLayout* pSalLayout = rOut.ImplLayout( rTextAct.GetText(), rTextAct.GetIndex(), + rTextAct.GetLen(), rTextAct.GetPoint(), + 0, rTextAct.GetDXArray() ); + if( pSalLayout ) + { + Rectangle aBoundRect( const_cast<OutputDevice&>(rOut).ImplGetTextBoundRect( *pSalLayout ) ); + aActionBounds = rOut.PixelToLogic( aBoundRect ); + pSalLayout->Release(); + } + } + } + break; + + case META_TEXTRECT_ACTION: + aActionBounds = static_cast<const MetaTextRectAction&>(rAct).GetRect(); + break; + + case META_STRETCHTEXT_ACTION: + { + const MetaStretchTextAction& rTextAct = static_cast<const MetaStretchTextAction&>(rAct); + const XubString aString( rTextAct.GetText(), rTextAct.GetIndex(), rTextAct.GetLen() ); + const long nLen = aString.Len(); + + // #i16195# Literate copy from TextArray action, the + // semantics for the ImplLayout call are copied from the + // OutDev::DrawStretchText() code. Unfortunately, also in + // this case, public outdev methods such as GetTextWidth() + // don't provide enough info. + if( nLen ) + { + // #105987# ImplLayout takes everything in logical coordinates + SalLayout* pSalLayout = rOut.ImplLayout( rTextAct.GetText(), rTextAct.GetIndex(), + rTextAct.GetLen(), rTextAct.GetPoint(), + rTextAct.GetWidth() ); + if( pSalLayout ) + { + Rectangle aBoundRect( const_cast<OutputDevice&>(rOut).ImplGetTextBoundRect( *pSalLayout ) ); + aActionBounds = rOut.PixelToLogic( aBoundRect ); + pSalLayout->Release(); + } + } + } + break; + + case META_TEXTLINE_ACTION: + DBG_ERROR("META_TEXTLINE_ACTION not supported"); + break; + + default: + break; + } + + if( !aActionBounds.IsEmpty() ) + return rOut.LogicToPixel( aActionBounds ); + else + return Rectangle(); +} + +static bool ImplIsActionHandlingTransparency( const MetaAction& rAct ) +{ + // META_FLOATTRANSPARENT_ACTION 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 META_TRANSPARENT_ACTION: + case META_BMPEX_ACTION: + case META_BMPEXSCALE_ACTION: + case META_BMPEXSCALEPART_ACTION: + return true; + + default: + return false; + } +} + +// remove comment to enable highlighting of generated output +bool OutputDevice::RemoveTransparenciesFromMetaFile( const GDIMetaFile& rInMtf, GDIMetaFile& rOutMtf, + long nMaxBmpDPIX, long nMaxBmpDPIY, + bool bReduceTransparency, bool bTransparencyAutoMode, + bool bDownsampleBitmaps, + const Color& rBackground + ) +{ + MetaAction* pCurrAct; + bool bTransparent( false ); + + rOutMtf.Clear(); + + if( ! bReduceTransparency || bTransparencyAutoMode ) + { + // watch for transparent drawing actions + for( pCurrAct = ( (GDIMetaFile&) rInMtf ).FirstAction(); + pCurrAct && !bTransparent; + pCurrAct = ( (GDIMetaFile&) rInMtf ).NextAction() ) + { + // #i10613# Extracted "specialness" predicate into extra method + + // #107169# Also examine metafiles with masked bitmaps in + // detail. Further down, this is optimized in such a way + // that there's no unnecessary painting of masked bitmaps + // (which are _always_ subdivided into rectangular regions + // of uniform opacity): if a masked bitmap is printed over + // empty background, we convert to a plain bitmap with + // white background. + if( ImplIsActionSpecial( *pCurrAct ) ) + { + bTransparent = true; + } + } + } + + // #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; + + // create an OutputDevice to record mapmode changes and the like + VirtualDevice aMapModeVDev; + aMapModeVDev.mnDPIX = mnDPIX; + aMapModeVDev.mnDPIY = mnDPIY; + aMapModeVDev.EnableOutput(FALSE); + + int nLastBgAction, nActionNum; + + // 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 + nActionNum=0; nLastBgAction=-1; + pCurrAct=const_cast<GDIMetaFile&>(rInMtf).FirstAction(); + if( rBackground != Color( COL_TRANSPARENT ) ) + { + aBackgroundComponent.aBgColor = rBackground; + if( meOutDevType == OUTDEV_PRINTER ) + { + Printer* pThis = dynamic_cast<Printer*>(this); + Point aPageOffset = pThis->GetPageOffsetPixel(); + aPageOffset = Point( 0, 0 ) - aPageOffset; + Size aSize = pThis->GetPaperSizePixel(); + aBackgroundComponent.aBounds = Rectangle( aPageOffset, aSize ); + } + else + aBackgroundComponent.aBounds = Rectangle( Point( 0, 0 ), GetOutputSizePixel() ); + } + while( pCurrAct && bStillBackground ) + { + switch( pCurrAct->GetType() ) + { + case META_RECT_ACTION: + { + 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 META_POLYGON_ACTION: + { + const Polygon aPoly( + static_cast<const MetaPolygonAction*>(pCurrAct)->GetPolygon()); + if( !basegfx::tools::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 META_POLYPOLYGON_ACTION: + { + const PolyPolygon aPoly( + static_cast<const MetaPolyPolygonAction*>(pCurrAct)->GetPolyPolygon()); + if( aPoly.Count() != 1 || + !basegfx::tools::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 META_WALLPAPER_ACTION: + { + 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 ); + + pCurrAct=const_cast<GDIMetaFile&>(rInMtf).NextAction(); + ++nActionNum; + } + + ConnectedComponentsList aCCList; // list containing distinct sets of connected components as elements. + + // create an OutputDevice to record mapmode changes and the like + VirtualDevice aMapModeVDev2; + aMapModeVDev2.mnDPIX = mnDPIX; + aMapModeVDev2.mnDPIY = mnDPIY; + aMapModeVDev2.EnableOutput(FALSE); + + // 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.push_back( + ::std::make_pair( + pCurrAct, nActionNum) ); + + // execute action to get correct MapModes etc. + pCurrAct->Execute( &aMapModeVDev2 ); + pCurrAct=const_cast<GDIMetaFile&>(rInMtf).NextAction(); + ++nActionNum; + } + + // + // STAGE 2: Generate connected components list + // =========================================== + // + + // 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( &aMapModeVDev2 ); + + // cache bounds of current action + const Rectangle aBBCurrAct( ImplCalcActionBounds(*pCurrAct, aMapModeVDev2) ); + + // accumulate collected bounds here, initialize with current action + 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, aMapModeVDev2); + + if( !aBBCurrAct.IsEmpty() && + !aTotalComponents.bIsFullyTransparent ) + { + if( !aBackgroundComponent.aComponentList.empty() && + !aBackgroundComponent.aBounds.IsInside(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 ); + + if( aBackgroundComponent.bIsSpecial ) + bTreatSpecial = true; + } + + ConnectedComponentsList::iterator aCurrCC; + const ConnectedComponentsList::iterator aLastCC( aCCList.end() ); + bool bSomeComponentsChanged; + + // 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( aCurrCC=aCCList.begin(); aCurrCC != aLastCC; ) + { + // first check if current element's bounds are + // empty. This ensures that empty actions are not + // merged into one component, as a matter of fact, + // they have no position. + + // #107169# Wholly transparent objects need + // not be considered for connected components, + // too. Just put each of them into a separate + // component. + if( !aCurrCC->aBounds.IsEmpty() && + !aCurrCC->bIsFullyTransparent && + aCurrCC->aBounds.IsOver( aTotalBounds ) ) + { + // union the intersecting aCCList element into aTotalComponents + + // calc union bounding box + aTotalBounds.Union( aCurrCC->aBounds ); + + // extract all aCurr actions to aTotalComponents + aTotalComponents.aComponentList.splice( aTotalComponents.aComponentList.end(), + aCurrCC->aComponentList ); + + if( aCurrCC->bIsSpecial ) + bTreatSpecial = true; + + // remove and delete aCurrCC element from list (we've now merged its content) + aCurrCC = aCCList.erase( aCurrCC ); + + // at least one component changed, need to rescan everything + bSomeComponentsChanged = true; + } + else + { + ++aCurrCC; + } + } + } + while( bSomeComponentsChanged ); + } + + // + // STAGE 2.2: Determine special state for cc element + // ================================================= + // + + // now test whether the whole connected component must be + // treated specially (i.e. rendered as a bitmap): if the + // added action is the very first action, or all actions + // before it are completely transparent, the connected + // component need not be treated specially, not even if + // the added action contains transparency. This is because + // painting of transparent objects on _white background_ + // works without alpha compositing (you just calculate the + // color). Note that for the test "all objects before me + // are transparent" no sorting is necessary, since the + // added metaaction pCurrAct is always in the order the + // metafile is painted. Generally, the order of the + // metaactions in the ConnectedComponents are not + // guaranteed to be the same as in the metafile. + if( bTreatSpecial ) + { + // prev component(s) special -> this one, too + aTotalComponents.bIsSpecial = true; + } + else if( !ImplIsActionSpecial( *pCurrAct ) ) + { + // added action and none of prev components special -> + // this one normal, too + aTotalComponents.bIsSpecial = false; + } + else + { + // added action is special and none of prev components + // special -> do the detailed tests + + // can the action handle transparency correctly + // (i.e. when painted on white background, does the + // action still look correct)? + if( !ImplIsActionHandlingTransparency( *pCurrAct ) ) + { + // no, action cannot handle its transparency on + // a printer device, render to bitmap + aTotalComponents.bIsSpecial = true; + } + else + { + // yes, action can handle its transparency, so + // check whether we're on white background + if( aTotalComponents.aComponentList.empty() ) + { + // nothing between pCurrAct and page + // background -> don't be special + aTotalComponents.bIsSpecial = false; + } + else + { + // #107169# Fixes abnove now ensure that _no_ + // object in the list is fully transparent. Thus, + // if the component list is not empty above, we + // must assume that we have to treat this + // component special. + + // there are non-transparent objects between + // pCurrAct and the empty sheet of paper -> be + // special, then + aTotalComponents.bIsSpecial = true; + } + } + } + + + // + // STAGE 2.3: Add newly generated CC list element + // ============================================== + // + + // set new bounds and add action to list + aTotalComponents.aBounds = aTotalBounds; + aTotalComponents.aComponentList.push_back( + ::std::make_pair( + pCurrAct, nActionNum) ); + + // add aTotalComponents as a new entry to aCCList + aCCList.push_back( aTotalComponents ); + + DBG_ASSERT( !aTotalComponents.aComponentList.empty(), + "Printer::GetPreparedMetaFile empty component" ); + DBG_ASSERT( !aTotalComponents.aBounds.IsEmpty() || + (aTotalComponents.aBounds.IsEmpty() && aTotalComponents.aComponentList.size() == 1), + "Printer::GetPreparedMetaFile non-output generating actions must be solitary"); + DBG_ASSERT( !aTotalComponents.bIsFullyTransparent || + (aTotalComponents.bIsFullyTransparent && aTotalComponents.aComponentList.size() == 1), + "Printer::GetPreparedMetaFile fully transparent actions must be solitary"); + } + + // well now, we've got the list of disjunct connected + // components. Now we've got to create a map, which contains + // the corresponding aCCList element for every + // metaaction. Later on, we always process the complete + // metafile for each bitmap to be generated, but switch on + // output only for actions contained in the then current + // aCCList element. This ensures correct mapmode and attribute + // settings for all cases. + + // maps mtf actions to CC list entries + ::std::vector< const ConnectedComponents* > aCCList_MemberMap( rInMtf.GetActionCount() ); + + // iterate over all aCCList members and their contained metaactions + ConnectedComponentsList::iterator aCurr( aCCList.begin() ); + const ConnectedComponentsList::iterator aLast( aCCList.end() ); + for( ; aCurr != aLast; ++aCurr ) + { + ComponentList::iterator aCurrentAction( aCurr->aComponentList.begin() ); + const ComponentList::iterator aLastAction( aCurr->aComponentList.end() ); + for( ; aCurrentAction != aLastAction; ++aCurrentAction ) + { + // set pointer to aCCList element for corresponding index + aCCList_MemberMap[ aCurrentAction->second ] = &(*aCurr); + } + } + + // + // STAGE 3.1: Output background mtf actions (if there are any) + // =========================================================== + // + + ComponentList::iterator aCurrAct( aBackgroundComponent.aComponentList.begin() ); + const ComponentList::iterator aLastAct( aBackgroundComponent.aComponentList.end() ); + for( ; aCurrAct != aLastAct; ++aCurrAct ) + { + // simply add this action (above, we inserted the actions + // starting at index 0 up to and including nLastBgAction) + rOutMtf.AddAction( ( aCurrAct->first->Duplicate(), aCurrAct->first ) ); + } + + + // + // STAGE 3.2: Generate banded bitmaps for special regions + // ==================================================== + // + + Point aPageOffset; + Size aTmpSize( GetOutputSizePixel() ); + if( mpPDFWriter ) + { + aTmpSize = mpPDFWriter->getCurPageSize(); + aTmpSize = LogicToPixel( aTmpSize, MapMode( MAP_POINT ) ); + + // also add error code to PDFWriter + mpPDFWriter->insertError( vcl::PDFWriter::Warning_Transparency_Converted ); + } + else if( meOutDevType == OUTDEV_PRINTER ) + { + Printer* pThis = dynamic_cast<Printer*>(this); + aPageOffset = pThis->GetPageOffsetPixel(); + aPageOffset = Point( 0, 0 ) - aPageOffset; + aTmpSize = pThis->GetPaperSizePixel(); + } + const Rectangle aOutputRect( aPageOffset, aTmpSize ); + bool bTiling = dynamic_cast<Printer*>(this) != NULL; + + // iterate over all aCCList members and generate bitmaps for the special ones + for( aCurr = aCCList.begin(); aCurr != aLast; ++aCurr ) + { + if( aCurr->bIsSpecial ) + { + Rectangle aBoundRect( aCurr->aBounds ); + aBoundRect.Intersection( aOutputRect ); + + const double fBmpArea( (double) aBoundRect.GetWidth() * aBoundRect.GetHeight() ); + const double fOutArea( (double) aOutputRect.GetWidth() * aOutputRect.GetHeight() ); + + // check if output doesn't exceed given size + if( bReduceTransparency && bTransparencyAutoMode && ( fBmpArea > ( 0.25 * fOutArea ) ) ) + { + // output normally. Therefore, we simply clear the + // special attribute, as everything non-special is + // copied to rOutMtf further below. + aCurr->bIsSpecial = false; + } + else + { + // create new bitmap action first + if( aBoundRect.GetWidth() && aBoundRect.GetHeight() ) + { + Point aDstPtPix( aBoundRect.TopLeft() ); + Size aDstSzPix; + + VirtualDevice aMapVDev; // here, we record only mapmode information + aMapVDev.EnableOutput(FALSE); + + VirtualDevice aPaintVDev; // into this one, we render. + + rOutMtf.AddAction( new MetaPushAction( PUSH_MAPMODE ) ); + rOutMtf.AddAction( new MetaMapModeAction() ); + + aPaintVDev.SetDrawMode( GetDrawMode() ); + + while( aDstPtPix.Y() <= aBoundRect.Bottom() ) + { + aDstPtPix.X() = aBoundRect.Left(); + aDstSzPix = bTiling ? Size( MAX_TILE_WIDTH, MAX_TILE_HEIGHT ) : aBoundRect.GetSize(); + + if( ( aDstPtPix.Y() + aDstSzPix.Height() - 1L ) > aBoundRect.Bottom() ) + aDstSzPix.Height() = aBoundRect.Bottom() - aDstPtPix.Y() + 1L; + + while( aDstPtPix.X() <= aBoundRect.Right() ) + { + if( ( aDstPtPix.X() + aDstSzPix.Width() - 1L ) > aBoundRect.Right() ) + aDstSzPix.Width() = aBoundRect.Right() - aDstPtPix.X() + 1L; + + if( !Rectangle( aDstPtPix, aDstSzPix ).Intersection( aBoundRect ).IsEmpty() && + aPaintVDev.SetOutputSizePixel( aDstSzPix ) ) + { + aPaintVDev.Push(); + aMapVDev.Push(); + + aMapVDev.mnDPIX = aPaintVDev.mnDPIX = mnDPIX; + aMapVDev.mnDPIY = aPaintVDev.mnDPIY = mnDPIY; + + aPaintVDev.EnableOutput(FALSE); + + // iterate over all actions + for( pCurrAct=const_cast<GDIMetaFile&>(rInMtf).FirstAction(), nActionNum=0; + pCurrAct; + pCurrAct=const_cast<GDIMetaFile&>(rInMtf).NextAction(), ++nActionNum ) + { + // enable output only for + // actions that are members of + // the current aCCList element + // (aCurr) + if( aCCList_MemberMap[nActionNum] == &(*aCurr) ) + aPaintVDev.EnableOutput(TRUE); + + // but process every action + const USHORT nType( pCurrAct->GetType() ); + + if( META_MAPMODE_ACTION == nType ) + { + pCurrAct->Execute( &aMapVDev ); + + MapMode aMtfMap( aMapVDev.GetMapMode() ); + const Point aNewOrg( aMapVDev.PixelToLogic( aDstPtPix ) ); + + aMtfMap.SetOrigin( Point( -aNewOrg.X(), -aNewOrg.Y() ) ); + aPaintVDev.SetMapMode( aMtfMap ); + } + else if( ( META_PUSH_ACTION == nType ) || ( META_POP_ACTION ) == nType ) + { + pCurrAct->Execute( &aMapVDev ); + pCurrAct->Execute( &aPaintVDev ); + } + else if( META_GRADIENT_ACTION == nType ) + { + MetaGradientAction* pGradientAction = static_cast<MetaGradientAction*>(pCurrAct); + Printer* pPrinter = dynamic_cast< Printer* >(this); + if( pPrinter ) + pPrinter->DrawGradientEx( &aPaintVDev, pGradientAction->GetRect(), pGradientAction->GetGradient() ); + else + DrawGradient( pGradientAction->GetRect(), pGradientAction->GetGradient() ); + } + else + { + pCurrAct->Execute( &aPaintVDev ); + } + + if( !( nActionNum % 8 ) ) + Application::Reschedule(); + } + + const BOOL bOldMap = mbMap; + mbMap = aPaintVDev.mbMap = FALSE; + + Bitmap aBandBmp( aPaintVDev.GetBitmap( Point(), aDstSzPix ) ); + + // scale down bitmap, if requested + if( bDownsampleBitmaps ) + { + aBandBmp = GetDownsampledBitmap( aDstSzPix, + Point(), aBandBmp.GetSizePixel(), + aBandBmp, nMaxBmpDPIX, nMaxBmpDPIY ); + } + + rOutMtf.AddAction( new MetaCommentAction( "PRNSPOOL_TRANSPARENTBITMAP_BEGIN" ) ); + rOutMtf.AddAction( new MetaBmpScaleAction( aDstPtPix, aDstSzPix, aBandBmp ) ); + rOutMtf.AddAction( new MetaCommentAction( "PRNSPOOL_TRANSPARENTBITMAP_END" ) ); + + aPaintVDev.mbMap = TRUE; + mbMap = bOldMap; + aMapVDev.Pop(); + aPaintVDev.Pop(); + } + + // overlapping bands to avoid missing lines (e.g. PostScript) + aDstPtPix.X() += aDstSzPix.Width(); + } + + // overlapping bands to avoid missing lines (e.g. PostScript) + aDstPtPix.Y() += aDstSzPix.Height(); + } + + rOutMtf.AddAction( new MetaPopAction() ); + } + } + } + } + + // + // STAGE 4: Copy actions to output metafile + // ======================================== + // + + // create an OutputDevice to record color settings, mapmode + // changes and the like + VirtualDevice aMapModeVDev3; + aMapModeVDev3.mnDPIX = mnDPIX; + aMapModeVDev3.mnDPIY = mnDPIY; + aMapModeVDev3.EnableOutput(FALSE); + + // iterate over all actions and duplicate the ones not in a + // special aCCList member into rOutMtf + for( pCurrAct=const_cast<GDIMetaFile&>(rInMtf).FirstAction(), nActionNum=0; + pCurrAct; + pCurrAct=const_cast<GDIMetaFile&>(rInMtf).NextAction(), ++nActionNum ) + { + const ConnectedComponents* pCurrAssociatedComponent = aCCList_MemberMap[nActionNum]; + + // NOTE: This relies on the fact that map-mode or draw + // mode changing actions are solitary aCCList elements and + // have empty bounding boxes, see comment on stage 2.1 + // above + if( pCurrAssociatedComponent && + (pCurrAssociatedComponent->aBounds.IsEmpty() || + !pCurrAssociatedComponent->bIsSpecial) ) + { + // #107169# Treat transparent bitmaps special, if they + // are the first (or sole) action in their bounds + // list. Note that we previously ensured that no + // fully-transparent objects are before us here. + if( ImplIsActionHandlingTransparency( *pCurrAct ) && + pCurrAssociatedComponent->aComponentList.begin()->first == pCurrAct ) + { + // convert actions, where masked-out parts are of + // given background color + ImplConvertTransparentAction(rOutMtf, + *pCurrAct, + aMapModeVDev3, + aBackgroundComponent.aBgColor); + } + else + { + // simply add this action + rOutMtf.AddAction( ( pCurrAct->Duplicate(), pCurrAct ) ); + } + + pCurrAct->Execute(&aMapModeVDev3); + } + } + + rOutMtf.SetPrefMapMode( rInMtf.GetPrefMapMode() ); + rOutMtf.SetPrefSize( rInMtf.GetPrefSize() ); + } + return bTransparent; +} + +// ----------------------------------------------------------------------------- + +Bitmap OutputDevice::GetDownsampledBitmap( const Size& rDstSz, + const Point& rSrcPt, const Size& rSrcSz, + const Bitmap& rBmp, long nMaxBmpDPIX, long nMaxBmpDPIY ) +{ + Bitmap aBmp( rBmp ); + + if( !aBmp.IsEmpty() ) + { + Point aPoint; + const Rectangle aBmpRect( aPoint, aBmp.GetSizePixel() ); + Rectangle aSrcRect( rSrcPt, rSrcSz ); + + // do cropping if neccessary + if( aSrcRect.Intersection( aBmpRect ) != aBmpRect ) + { + if( !aSrcRect.IsEmpty() ) + aBmp.Crop( aSrcRect ); + else + aBmp.SetEmpty(); + } + + if( !aBmp.IsEmpty() ) + { + // do downsampling if neccessary + Size aDstSizeTwip( PixelToLogic( LogicToPixel( rDstSz ), MAP_TWIP ) ); + + // #103209# Normalize size (mirroring has to happen outside of this method) + aDstSizeTwip = Size( labs(aDstSizeTwip.Width()), labs(aDstSizeTwip.Height()) ); + + const Size aBmpSize( aBmp.GetSizePixel() ); + const double fBmpPixelX = aBmpSize.Width(); + const double fBmpPixelY = aBmpSize.Height(); + const double fMaxPixelX = aDstSizeTwip.Width() * nMaxBmpDPIX / 1440.0; + const double fMaxPixelY = aDstSizeTwip.Height() * nMaxBmpDPIY / 1440.0; + + // 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.Width() = FRound( fMaxPixelY * fBmpWH ); + aNewBmpSize.Height() = FRound( fMaxPixelY ); + } + else if( fBmpWH > 0.0 ) + { + aNewBmpSize.Width() = FRound( fMaxPixelX ); + aNewBmpSize.Height() = FRound( fMaxPixelX / fBmpWH); + } + + if( aNewBmpSize.Width() && aNewBmpSize.Height() ) + aBmp.Scale( aNewBmpSize ); + else + aBmp.SetEmpty(); + } + } + } + + return aBmp; +} + +// ----------------------------------------------------------------------------- + +BitmapEx OutputDevice::GetDownsampledBitmapEx( const Size& rDstSz, + const Point& rSrcPt, const Size& rSrcSz, + const BitmapEx& rBmpEx, long nMaxBmpDPIX, long nMaxBmpDPIY ) +{ + BitmapEx aBmpEx( rBmpEx ); + + if( !aBmpEx.IsEmpty() ) + { + Point aPoint; + const Rectangle aBmpRect( aPoint, aBmpEx.GetSizePixel() ); + Rectangle aSrcRect( rSrcPt, rSrcSz ); + + // do cropping if neccessary + if( aSrcRect.Intersection( aBmpRect ) != aBmpRect ) + { + if( !aSrcRect.IsEmpty() ) + aBmpEx.Crop( aSrcRect ); + else + aBmpEx.SetEmpty(); + } + + if( !aBmpEx.IsEmpty() ) + { + // do downsampling if neccessary + Size aDstSizeTwip( PixelToLogic( LogicToPixel( rDstSz ), MAP_TWIP ) ); + + // #103209# Normalize size (mirroring has to happen outside of this method) + aDstSizeTwip = Size( labs(aDstSizeTwip.Width()), labs(aDstSizeTwip.Height()) ); + + const Size aBmpSize( aBmpEx.GetSizePixel() ); + const double fBmpPixelX = aBmpSize.Width(); + const double fBmpPixelY = aBmpSize.Height(); + const double fMaxPixelX = aDstSizeTwip.Width() * nMaxBmpDPIX / 1440.0; + const double fMaxPixelY = aDstSizeTwip.Height() * nMaxBmpDPIY / 1440.0; + + // 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.Width() = FRound( fMaxPixelY * fBmpWH ); + aNewBmpSize.Height() = FRound( fMaxPixelY ); + } + else if( fBmpWH > 0.0 ) + { + aNewBmpSize.Width() = FRound( fMaxPixelX ); + aNewBmpSize.Height() = FRound( fMaxPixelX / fBmpWH); + } + + if( aNewBmpSize.Width() && aNewBmpSize.Height() ) + aBmpEx.Scale( aNewBmpSize ); + else + aBmpEx.SetEmpty(); + } + } + } + + return aBmpEx; +} + +// ----------------------------------------------------------------------------- + +void Printer::DrawGradientEx( OutputDevice* pOut, const Rectangle& rRect, const Gradient& rGradient ) +{ + const PrinterOptions& rPrinterOptions = GetPrinterOptions(); + + if( rPrinterOptions.IsReduceGradients() ) + { + if( PRINTER_GRADIENT_STRIPES == rPrinterOptions.GetReducedGradientMode() ) + { + if( !rGradient.GetSteps() || ( rGradient.GetSteps() > rPrinterOptions.GetReducedGradientStepCount() ) ) + { + Gradient aNewGradient( rGradient ); + + aNewGradient.SetSteps( rPrinterOptions.GetReducedGradientStepCount() ); + pOut->DrawGradient( rRect, aNewGradient ); + } + else + pOut->DrawGradient( rRect, rGradient ); + } + else + { + const Color& rStartColor = rGradient.GetStartColor(); + const Color& rEndColor = rGradient.GetEndColor(); + const long nR = ( ( (long) rStartColor.GetRed() * rGradient.GetStartIntensity() ) / 100L + + ( (long) rEndColor.GetRed() * rGradient.GetEndIntensity() ) / 100L ) >> 1; + const long nG = ( ( (long) rStartColor.GetGreen() * rGradient.GetStartIntensity() ) / 100L + + ( (long) rEndColor.GetGreen() * rGradient.GetEndIntensity() ) / 100L ) >> 1; + const long nB = ( ( (long) rStartColor.GetBlue() * rGradient.GetStartIntensity() ) / 100L + + ( (long) rEndColor.GetBlue() * rGradient.GetEndIntensity() ) / 100L ) >> 1; + const Color aColor( (BYTE) nR, (BYTE) nG, (BYTE) nB ); + + pOut->Push( PUSH_LINECOLOR | PUSH_FILLCOLOR ); + pOut->SetLineColor( aColor ); + pOut->SetFillColor( aColor ); + pOut->DrawRect( rRect ); + pOut->Pop(); + } + } + else + pOut->DrawGradient( rRect, rGradient ); +} + +// ----------------------------------------------------------------------------- + +void Printer::DrawGradientEx( OutputDevice* pOut, const PolyPolygon& rPolyPoly, const Gradient& rGradient ) +{ + const PrinterOptions& rPrinterOptions = GetPrinterOptions(); + + if( rPrinterOptions.IsReduceGradients() ) + { + if( PRINTER_GRADIENT_STRIPES == rPrinterOptions.GetReducedGradientMode() ) + { + if( !rGradient.GetSteps() || ( rGradient.GetSteps() > rPrinterOptions.GetReducedGradientStepCount() ) ) + { + Gradient aNewGradient( rGradient ); + + aNewGradient.SetSteps( rPrinterOptions.GetReducedGradientStepCount() ); + pOut->DrawGradient( rPolyPoly, aNewGradient ); + } + else + pOut->DrawGradient( rPolyPoly, rGradient ); + } + else + { + const Color& rStartColor = rGradient.GetStartColor(); + const Color& rEndColor = rGradient.GetEndColor(); + const long nR = ( ( (long) rStartColor.GetRed() * rGradient.GetStartIntensity() ) / 100L + + ( (long) rEndColor.GetRed() * rGradient.GetEndIntensity() ) / 100L ) >> 1; + const long nG = ( ( (long) rStartColor.GetGreen() * rGradient.GetStartIntensity() ) / 100L + + ( (long) rEndColor.GetGreen() * rGradient.GetEndIntensity() ) / 100L ) >> 1; + const long nB = ( ( (long) rStartColor.GetBlue() * rGradient.GetStartIntensity() ) / 100L + + ( (long) rEndColor.GetBlue() * rGradient.GetEndIntensity() ) / 100L ) >> 1; + const Color aColor( (BYTE) nR, (BYTE) nG, (BYTE) nB ); + + pOut->Push( PUSH_LINECOLOR | PUSH_FILLCOLOR ); + pOut->SetLineColor( aColor ); + pOut->SetFillColor( aColor ); + pOut->DrawPolyPolygon( rPolyPoly ); + pOut->Pop(); + } + } + else + pOut->DrawGradient( rPolyPoly, rGradient ); +} |