blob: 50d7de111e1eba7c64c7ad8a95c7524fdac8c116 [file] [log] [blame]
/**************************************************************
*
* 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
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the 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/salbtype.hxx>
#include <vcl/print.hxx>
#include <vcl/svapp.hxx>
#include <vcl/bmpacc.hxx>
#include <print.h>
#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);
sal_uInt16 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<sal_uInt8>( (255L*nTransparency + (100L - nTransparency)*aLineColor.GetRed()) / 100L ) );
aLineColor.SetGreen( static_cast<sal_uInt8>( (255L*nTransparency + (100L - nTransparency)*aLineColor.GetGreen()) / 100L ) );
aLineColor.SetBlue( static_cast<sal_uInt8>( (255L*nTransparency + (100L - nTransparency)*aLineColor.GetBlue()) / 100L ) );
o_rMtf.AddAction( new MetaLineColorAction(aLineColor, sal_True) );
Color aFillColor( rStateOutDev.GetFillColor() );
aFillColor.SetRed( static_cast<sal_uInt8>( (255L*nTransparency + (100L - nTransparency)*aFillColor.GetRed()) / 100L ) );
aFillColor.SetGreen( static_cast<sal_uInt8>( (255L*nTransparency + (100L - nTransparency)*aFillColor.GetGreen()) / 100L ) );
aFillColor.SetBlue( static_cast<sal_uInt8>( (255L*nTransparency + (100L - nTransparency)*aFillColor.GetBlue()) / 100L ) );
o_rMtf.AddAction( new MetaFillColorAction(aFillColor, sal_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() );
aActionBounds.Justify();
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(sal_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;
}
// clean up aMapModeVDev
sal_uInt32 nCount = aMapModeVDev.GetGCStackDepth();
while( nCount-- )
aMapModeVDev.Pop();
ConnectedComponentsList aCCList; // list containing distinct sets of connected components as elements.
// 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( &aMapModeVDev );
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( &aMapModeVDev );
// cache bounds of current action
const Rectangle aBBCurrAct( ImplCalcActionBounds(*pCurrAct, aMapModeVDev) );
// 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, aMapModeVDev);
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(sal_False);
VirtualDevice aPaintVDev; // into this one, we render.
aPaintVDev.SetBackground( aBackgroundComponent.aBgColor );
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(sal_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(sal_True);
// but process every action
const sal_uInt16 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 sal_Bool bOldMap = mbMap;
mbMap = aPaintVDev.mbMap = sal_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 = sal_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() );
}
}
}
}
// clean up aMapModeVDev
nCount = aMapModeVDev.GetGCStackDepth();
while( nCount-- )
aMapModeVDev.Pop();
//
// STAGE 4: Copy actions to output metafile
// ========================================
//
// 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,
aMapModeVDev,
aBackgroundComponent.aBgColor);
}
else
{
// simply add this action
rOutMtf.AddAction( ( pCurrAct->Duplicate(), pCurrAct ) );
}
pCurrAct->Execute(&aMapModeVDev);
}
}
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( (sal_uInt8) nR, (sal_uInt8) nG, (sal_uInt8) 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( (sal_uInt8) nR, (sal_uInt8) nG, (sal_uInt8) nB );
pOut->Push( PUSH_LINECOLOR | PUSH_FILLCOLOR );
pOut->SetLineColor( aColor );
pOut->SetFillColor( aColor );
pOut->DrawPolyPolygon( rPolyPoly );
pOut->Pop();
}
}
else
pOut->DrawGradient( rPolyPoly, rGradient );
}