nss: upgrade to release 3.73
[LibreOffice.git] / vcl / source / outdev / bitmap.cxx
blob796b3b82f555eb7ec4ea7df3df2cc13039e9cd6a
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <cassert>
21 #include <cstdlib>
23 #include <vcl/bitmap.hxx>
24 #include <vcl/bitmapex.hxx>
25 #include <vcl/BitmapFilterStackBlur.hxx>
26 #include <vcl/bitmapaccess.hxx>
27 #include <vcl/canvastools.hxx>
28 #include <vcl/gdimtf.hxx>
29 #include <vcl/metaact.hxx>
30 #include <config_features.h>
31 #if HAVE_FEATURE_OPENGL
32 #include <vcl/opengl/OpenGLHelper.hxx>
33 #endif
34 #include <vcl/skia/SkiaHelper.hxx>
35 #include <vcl/outdev.hxx>
36 #include <vcl/virdev.hxx>
37 #include <vcl/image.hxx>
38 #include <vcl/BitmapMonochromeFilter.hxx>
40 #include <bmpfast.hxx>
41 #include <salgdi.hxx>
42 #include <salbmp.hxx>
44 #include <basegfx/matrix/b2dhommatrixtools.hxx>
45 #include <memory>
46 #include <comphelper/lok.hxx>
47 #include <bitmapwriteaccess.hxx>
48 #include <sal/log.hxx>
49 #include <osl/diagnose.h>
50 #include <tools/helpers.hxx>
51 #include <tools/debug.hxx>
52 #include <rtl/math.hxx>
54 #include <vcl/dibtools.hxx>
55 #include <tools/stream.hxx>
57 void OutputDevice::DrawBitmap( const Point& rDestPt, const Bitmap& rBitmap )
59 assert(!is_double_buffered_window());
61 const Size aSizePix( rBitmap.GetSizePixel() );
62 DrawBitmap( rDestPt, PixelToLogic( aSizePix ), Point(), aSizePix, rBitmap, MetaActionType::BMP );
65 void OutputDevice::DrawBitmap( const Point& rDestPt, const Size& rDestSize, const Bitmap& rBitmap )
67 assert(!is_double_buffered_window());
69 DrawBitmap( rDestPt, rDestSize, Point(), rBitmap.GetSizePixel(), rBitmap, MetaActionType::BMPSCALE );
73 void OutputDevice::DrawBitmap( const Point& rDestPt, const Size& rDestSize,
74 const Point& rSrcPtPixel, const Size& rSrcSizePixel,
75 const Bitmap& rBitmap, const MetaActionType nAction )
77 assert(!is_double_buffered_window());
79 if( ImplIsRecordLayout() )
80 return;
82 if ( RasterOp::Invert == meRasterOp )
84 DrawRect( tools::Rectangle( rDestPt, rDestSize ) );
85 return;
88 Bitmap aBmp( rBitmap );
90 if ( mnDrawMode & ( DrawModeFlags::BlackBitmap | DrawModeFlags::WhiteBitmap |
91 DrawModeFlags::GrayBitmap ) )
93 if ( mnDrawMode & ( DrawModeFlags::BlackBitmap | DrawModeFlags::WhiteBitmap ) )
95 sal_uInt8 cCmpVal;
97 if ( mnDrawMode & DrawModeFlags::BlackBitmap )
98 cCmpVal = 0;
99 else
100 cCmpVal = 255;
102 Color aCol( cCmpVal, cCmpVal, cCmpVal );
103 Push( PushFlags::LINECOLOR | PushFlags::FILLCOLOR );
104 SetLineColor( aCol );
105 SetFillColor( aCol );
106 DrawRect( tools::Rectangle( rDestPt, rDestSize ) );
107 Pop();
108 return;
110 else if( !!aBmp )
112 if ( mnDrawMode & DrawModeFlags::GrayBitmap )
113 aBmp.Convert( BmpConversion::N8BitGreys );
117 if ( mpMetaFile )
119 switch( nAction )
121 case MetaActionType::BMP:
122 mpMetaFile->AddAction( new MetaBmpAction( rDestPt, aBmp ) );
123 break;
125 case MetaActionType::BMPSCALE:
126 mpMetaFile->AddAction( new MetaBmpScaleAction( rDestPt, rDestSize, aBmp ) );
127 break;
129 case MetaActionType::BMPSCALEPART:
130 mpMetaFile->AddAction( new MetaBmpScalePartAction(
131 rDestPt, rDestSize, rSrcPtPixel, rSrcSizePixel, aBmp ) );
132 break;
134 default: break;
138 if ( !IsDeviceOutputNecessary() )
139 return;
141 if ( !mpGraphics && !AcquireGraphics() )
142 return;
144 if ( mbInitClipRegion )
145 InitClipRegion();
147 if ( mbOutputClipped )
148 return;
150 if( !aBmp.IsEmpty() )
152 SalTwoRect aPosAry(rSrcPtPixel.X(), rSrcPtPixel.Y(), rSrcSizePixel.Width(), rSrcSizePixel.Height(),
153 ImplLogicXToDevicePixel(rDestPt.X()), ImplLogicYToDevicePixel(rDestPt.Y()),
154 ImplLogicWidthToDevicePixel(rDestSize.Width()),
155 ImplLogicHeightToDevicePixel(rDestSize.Height()));
157 if ( aPosAry.mnSrcWidth && aPosAry.mnSrcHeight && aPosAry.mnDestWidth && aPosAry.mnDestHeight )
159 const BmpMirrorFlags nMirrFlags = AdjustTwoRect( aPosAry, aBmp.GetSizePixel() );
161 if ( nMirrFlags != BmpMirrorFlags::NONE )
162 aBmp.Mirror( nMirrFlags );
164 if ( aPosAry.mnSrcWidth && aPosAry.mnSrcHeight && aPosAry.mnDestWidth && aPosAry.mnDestHeight )
166 if ( nAction == MetaActionType::BMPSCALE )
167 ScaleBitmap (aBmp, aPosAry);
169 mpGraphics->DrawBitmap( aPosAry, *aBmp.ImplGetSalBitmap(), this );
174 if( mpAlphaVDev )
176 // #i32109#: Make bitmap area opaque
177 mpAlphaVDev->ImplFillOpaqueRectangle( tools::Rectangle(rDestPt, rDestSize) );
181 Bitmap OutputDevice::GetDownsampledBitmap( const Size& rDstSz,
182 const Point& rSrcPt, const Size& rSrcSz,
183 const Bitmap& rBmp, tools::Long nMaxBmpDPIX, tools::Long nMaxBmpDPIY )
185 Bitmap aBmp( rBmp );
187 if( !aBmp.IsEmpty() )
189 const tools::Rectangle aBmpRect( Point(), aBmp.GetSizePixel() );
190 tools::Rectangle aSrcRect( rSrcPt, rSrcSz );
192 // do cropping if necessary
193 if( aSrcRect.Intersection( aBmpRect ) != aBmpRect )
195 if( !aSrcRect.IsEmpty() )
196 aBmp.Crop( aSrcRect );
197 else
198 aBmp.SetEmpty();
201 if( !aBmp.IsEmpty() )
203 // do downsampling if necessary
204 Size aDstSizeTwip( PixelToLogic(LogicToPixel(rDstSz), MapMode(MapUnit::MapTwip)) );
206 // #103209# Normalize size (mirroring has to happen outside of this method)
207 aDstSizeTwip = Size( std::abs(aDstSizeTwip.Width()), std::abs(aDstSizeTwip.Height()) );
209 const Size aBmpSize( aBmp.GetSizePixel() );
210 const double fBmpPixelX = aBmpSize.Width();
211 const double fBmpPixelY = aBmpSize.Height();
212 const double fMaxPixelX = aDstSizeTwip.Width() * nMaxBmpDPIX / 1440.0;
213 const double fMaxPixelY = aDstSizeTwip.Height() * nMaxBmpDPIY / 1440.0;
215 // check, if the bitmap DPI exceeds the maximum DPI (allow 4 pixel rounding tolerance)
216 if( ( ( fBmpPixelX > ( fMaxPixelX + 4 ) ) ||
217 ( fBmpPixelY > ( fMaxPixelY + 4 ) ) ) &&
218 ( fBmpPixelY > 0.0 ) && ( fMaxPixelY > 0.0 ) )
220 // do scaling
221 Size aNewBmpSize;
222 const double fBmpWH = fBmpPixelX / fBmpPixelY;
223 const double fMaxWH = fMaxPixelX / fMaxPixelY;
225 if( fBmpWH < fMaxWH )
227 aNewBmpSize.setWidth( FRound( fMaxPixelY * fBmpWH ) );
228 aNewBmpSize.setHeight( FRound( fMaxPixelY ) );
230 else if( fBmpWH > 0.0 )
232 aNewBmpSize.setWidth( FRound( fMaxPixelX ) );
233 aNewBmpSize.setHeight( FRound( fMaxPixelX / fBmpWH) );
236 if( aNewBmpSize.Width() && aNewBmpSize.Height() )
237 aBmp.Scale( aNewBmpSize );
238 else
239 aBmp.SetEmpty();
244 return aBmp;
247 void OutputDevice::DrawBitmapEx( const Point& rDestPt,
248 const BitmapEx& rBitmapEx )
250 assert(!is_double_buffered_window());
252 if( ImplIsRecordLayout() )
253 return;
255 if( TransparentType::NONE == rBitmapEx.GetTransparentType() )
257 DrawBitmap( rDestPt, rBitmapEx.GetBitmap() );
259 else
261 const Size aSizePix( rBitmapEx.GetSizePixel() );
262 DrawBitmapEx( rDestPt, PixelToLogic( aSizePix ), Point(), aSizePix, rBitmapEx, MetaActionType::BMPEX );
266 void OutputDevice::DrawBitmapEx( const Point& rDestPt, const Size& rDestSize,
267 const BitmapEx& rBitmapEx )
269 assert(!is_double_buffered_window());
271 if( ImplIsRecordLayout() )
272 return;
274 if ( TransparentType::NONE == rBitmapEx.GetTransparentType() )
276 DrawBitmap( rDestPt, rDestSize, rBitmapEx.GetBitmap() );
278 else
280 DrawBitmapEx( rDestPt, rDestSize, Point(), rBitmapEx.GetSizePixel(), rBitmapEx, MetaActionType::BMPEXSCALE );
285 void OutputDevice::DrawBitmapEx( const Point& rDestPt, const Size& rDestSize,
286 const Point& rSrcPtPixel, const Size& rSrcSizePixel,
287 const BitmapEx& rBitmapEx, const MetaActionType nAction )
289 assert(!is_double_buffered_window());
291 if( ImplIsRecordLayout() )
292 return;
294 if( TransparentType::NONE == rBitmapEx.GetTransparentType() )
296 DrawBitmap( rDestPt, rDestSize, rSrcPtPixel, rSrcSizePixel, rBitmapEx.GetBitmap() );
298 else
300 if ( RasterOp::Invert == meRasterOp )
302 DrawRect( tools::Rectangle( rDestPt, rDestSize ) );
303 return;
306 BitmapEx aBmpEx( rBitmapEx );
308 if ( mnDrawMode & ( DrawModeFlags::BlackBitmap | DrawModeFlags::WhiteBitmap |
309 DrawModeFlags::GrayBitmap ) )
311 if ( mnDrawMode & ( DrawModeFlags::BlackBitmap | DrawModeFlags::WhiteBitmap ) )
313 Bitmap aColorBmp( aBmpEx.GetSizePixel(), 1 );
314 sal_uInt8 cCmpVal;
316 if ( mnDrawMode & DrawModeFlags::BlackBitmap )
317 cCmpVal = 0;
318 else
319 cCmpVal = 255;
321 aColorBmp.Erase( Color( cCmpVal, cCmpVal, cCmpVal ) );
323 if( aBmpEx.IsAlpha() )
325 // Create one-bit mask out of alpha channel, by
326 // thresholding it at alpha=0.5. As
327 // DRAWMODE_BLACK/WHITEBITMAP requires monochrome
328 // output, having alpha-induced grey levels is not
329 // acceptable.
330 BitmapEx aMaskEx(aBmpEx.GetAlpha().GetBitmap());
331 BitmapFilter::Filter(aMaskEx, BitmapMonochromeFilter(129));
332 aBmpEx = BitmapEx(aColorBmp, aMaskEx.GetBitmap());
334 else
336 aBmpEx = BitmapEx( aColorBmp, aBmpEx.GetMask() );
339 else if( !!aBmpEx )
341 if ( mnDrawMode & DrawModeFlags::GrayBitmap )
342 aBmpEx.Convert( BmpConversion::N8BitGreys );
346 if ( mpMetaFile )
348 switch( nAction )
350 case MetaActionType::BMPEX:
351 mpMetaFile->AddAction( new MetaBmpExAction( rDestPt, aBmpEx ) );
352 break;
354 case MetaActionType::BMPEXSCALE:
355 mpMetaFile->AddAction( new MetaBmpExScaleAction( rDestPt, rDestSize, aBmpEx ) );
356 break;
358 case MetaActionType::BMPEXSCALEPART:
359 mpMetaFile->AddAction( new MetaBmpExScalePartAction( rDestPt, rDestSize,
360 rSrcPtPixel, rSrcSizePixel, aBmpEx ) );
361 break;
363 default: break;
367 if ( !IsDeviceOutputNecessary() )
368 return;
370 if ( !mpGraphics && !AcquireGraphics() )
371 return;
373 if ( mbInitClipRegion )
374 InitClipRegion();
376 if ( mbOutputClipped )
377 return;
379 DrawDeviceBitmap( rDestPt, rDestSize, rSrcPtPixel, rSrcSizePixel, aBmpEx );
383 Bitmap OutputDevice::GetBitmap( const Point& rSrcPt, const Size& rSize ) const
385 Bitmap aBmp;
386 tools::Long nX = ImplLogicXToDevicePixel( rSrcPt.X() );
387 tools::Long nY = ImplLogicYToDevicePixel( rSrcPt.Y() );
388 tools::Long nWidth = ImplLogicWidthToDevicePixel( rSize.Width() );
389 tools::Long nHeight = ImplLogicHeightToDevicePixel( rSize.Height() );
391 if ( mpGraphics || AcquireGraphics() )
393 if ( nWidth > 0 && nHeight > 0 && nX <= (mnOutWidth + mnOutOffX) && nY <= (mnOutHeight + mnOutOffY))
395 tools::Rectangle aRect( Point( nX, nY ), Size( nWidth, nHeight ) );
396 bool bClipped = false;
398 // X-Coordinate outside of draw area?
399 if ( nX < mnOutOffX )
401 nWidth -= ( mnOutOffX - nX );
402 nX = mnOutOffX;
403 bClipped = true;
406 // Y-Coordinate outside of draw area?
407 if ( nY < mnOutOffY )
409 nHeight -= ( mnOutOffY - nY );
410 nY = mnOutOffY;
411 bClipped = true;
414 // Width outside of draw area?
415 if ( (nWidth + nX) > (mnOutWidth + mnOutOffX) )
417 nWidth = mnOutOffX + mnOutWidth - nX;
418 bClipped = true;
421 // Height outside of draw area?
422 if ( (nHeight + nY) > (mnOutHeight + mnOutOffY) )
424 nHeight = mnOutOffY + mnOutHeight - nY;
425 bClipped = true;
428 if ( bClipped )
430 // If the visible part has been clipped, we have to create a
431 // Bitmap with the correct size in which we copy the clipped
432 // Bitmap to the correct position.
433 ScopedVclPtrInstance< VirtualDevice > aVDev( *this );
435 if ( aVDev->SetOutputSizePixel( aRect.GetSize() ) )
437 if ( aVDev->mpGraphics || aVDev->AcquireGraphics() )
439 if ( (nWidth > 0) && (nHeight > 0) )
441 SalTwoRect aPosAry(nX, nY, nWidth, nHeight,
442 (aRect.Left() < mnOutOffX) ? (mnOutOffX - aRect.Left()) : 0L,
443 (aRect.Top() < mnOutOffY) ? (mnOutOffY - aRect.Top()) : 0L,
444 nWidth, nHeight);
445 aVDev->mpGraphics->CopyBits( aPosAry, mpGraphics, this, this );
447 else
449 OSL_ENSURE(false, "CopyBits with zero or negative width or height");
452 aBmp = aVDev->GetBitmap( Point(), aVDev->GetOutputSizePixel() );
454 else
455 bClipped = false;
457 else
458 bClipped = false;
461 if ( !bClipped )
463 std::shared_ptr<SalBitmap> pSalBmp = mpGraphics->GetBitmap( nX, nY, nWidth, nHeight, this );
465 if( pSalBmp )
467 aBmp.ImplSetSalBitmap(pSalBmp);
473 return aBmp;
476 BitmapEx OutputDevice::GetBitmapEx( const Point& rSrcPt, const Size& rSize ) const
479 // #110958# Extract alpha value from VDev, if any
480 if( mpAlphaVDev )
482 Bitmap aAlphaBitmap( mpAlphaVDev->GetBitmap( rSrcPt, rSize ) );
484 // ensure 8 bit alpha
485 if( aAlphaBitmap.GetBitCount() > 8 )
486 aAlphaBitmap.Convert( BmpConversion::N8BitNoConversion );
488 return BitmapEx(GetBitmap( rSrcPt, rSize ), AlphaMask( aAlphaBitmap ) );
490 else
491 return BitmapEx(GetBitmap( rSrcPt, rSize ));
494 void OutputDevice::DrawDeviceBitmap( const Point& rDestPt, const Size& rDestSize,
495 const Point& rSrcPtPixel, const Size& rSrcSizePixel,
496 BitmapEx& rBitmapEx )
498 assert(!is_double_buffered_window());
500 if (rBitmapEx.IsAlpha())
502 DrawDeviceAlphaBitmap(rBitmapEx.GetBitmap(), rBitmapEx.GetAlpha(), rDestPt, rDestSize, rSrcPtPixel, rSrcSizePixel);
504 else if (!!rBitmapEx)
506 SalTwoRect aPosAry(rSrcPtPixel.X(), rSrcPtPixel.Y(), rSrcSizePixel.Width(), rSrcSizePixel.Height(),
507 ImplLogicXToDevicePixel(rDestPt.X()), ImplLogicYToDevicePixel(rDestPt.Y()),
508 ImplLogicWidthToDevicePixel(rDestSize.Width()),
509 ImplLogicHeightToDevicePixel(rDestSize.Height()));
511 const BmpMirrorFlags nMirrFlags = AdjustTwoRect(aPosAry, rBitmapEx.GetSizePixel());
513 if (aPosAry.mnSrcWidth && aPosAry.mnSrcHeight && aPosAry.mnDestWidth && aPosAry.mnDestHeight)
516 if (nMirrFlags != BmpMirrorFlags::NONE)
517 rBitmapEx.Mirror(nMirrFlags);
519 const SalBitmap* pSalSrcBmp = rBitmapEx.ImplGetBitmapSalBitmap().get();
520 std::shared_ptr<SalBitmap> xMaskBmp = rBitmapEx.ImplGetMaskSalBitmap();
522 if (xMaskBmp)
524 bool bTryDirectPaint(pSalSrcBmp);
526 if (bTryDirectPaint && mpGraphics->DrawAlphaBitmap(aPosAry, *pSalSrcBmp, *xMaskBmp, this))
528 // tried to paint as alpha directly. If this worked, we are done (except
529 // alpha, see below)
531 else
533 // #4919452# reduce operation area to bounds of
534 // cliprect. since masked transparency involves
535 // creation of a large vdev and copying the screen
536 // content into that (slooow read from framebuffer),
537 // that should considerably increase performance for
538 // large bitmaps and small clippings.
540 // Note that this optimization is a workaround for a
541 // Writer peculiarity, namely, to decompose background
542 // graphics into myriads of disjunct, tiny
543 // rectangles. That otherwise kills us here, since for
544 // transparent output, SAL always prepares the whole
545 // bitmap, if aPosAry contains the whole bitmap (and
546 // it's _not_ to blame for that).
548 // Note the call to ImplPixelToDevicePixel(), since
549 // aPosAry already contains the mnOutOff-offsets, they
550 // also have to be applied to the region
551 tools::Rectangle aClipRegionBounds( ImplPixelToDevicePixel(maRegion).GetBoundRect() );
553 // TODO: Also respect scaling (that's a bit tricky,
554 // since the source points have to move fractional
555 // amounts (which is not possible, thus has to be
556 // emulated by increases copy area)
557 // const double nScaleX( aPosAry.mnDestWidth / aPosAry.mnSrcWidth );
558 // const double nScaleY( aPosAry.mnDestHeight / aPosAry.mnSrcHeight );
560 // for now, only identity scales allowed
561 if (!aClipRegionBounds.IsEmpty() &&
562 aPosAry.mnDestWidth == aPosAry.mnSrcWidth &&
563 aPosAry.mnDestHeight == aPosAry.mnSrcHeight)
565 // now intersect dest rect with clip region
566 aClipRegionBounds.Intersection(tools::Rectangle(aPosAry.mnDestX,
567 aPosAry.mnDestY,
568 aPosAry.mnDestX + aPosAry.mnDestWidth - 1,
569 aPosAry.mnDestY + aPosAry.mnDestHeight - 1));
571 // Note: I could theoretically optimize away the
572 // DrawBitmap below, if the region is empty
573 // here. Unfortunately, cannot rule out that
574 // somebody relies on the side effects.
575 if (!aClipRegionBounds.IsEmpty())
577 aPosAry.mnSrcX += aClipRegionBounds.Left() - aPosAry.mnDestX;
578 aPosAry.mnSrcY += aClipRegionBounds.Top() - aPosAry.mnDestY;
579 aPosAry.mnSrcWidth = aClipRegionBounds.GetWidth();
580 aPosAry.mnSrcHeight = aClipRegionBounds.GetHeight();
582 aPosAry.mnDestX = aClipRegionBounds.Left();
583 aPosAry.mnDestY = aClipRegionBounds.Top();
584 aPosAry.mnDestWidth = aClipRegionBounds.GetWidth();
585 aPosAry.mnDestHeight = aClipRegionBounds.GetHeight();
589 mpGraphics->DrawBitmap(aPosAry, *pSalSrcBmp, *xMaskBmp, this);
592 // #110958# Paint mask to alpha channel. Luckily, the
593 // black and white representation of the mask maps to
594 // the alpha channel
596 // #i25167# Restrict mask painting to _opaque_ areas
597 // of the mask, otherwise we spoil areas where no
598 // bitmap content was ever visible. Interestingly
599 // enough, this can be achieved by taking the mask as
600 // the transparency mask of itself
601 if (mpAlphaVDev)
602 mpAlphaVDev->DrawBitmapEx(rDestPt,
603 rDestSize,
604 BitmapEx(rBitmapEx.GetMask(),
605 rBitmapEx.GetMask()));
607 else
609 mpGraphics->DrawBitmap(aPosAry, *pSalSrcBmp, this);
611 if (mpAlphaVDev)
613 // #i32109#: Make bitmap area opaque
614 mpAlphaVDev->ImplFillOpaqueRectangle( tools::Rectangle(rDestPt, rDestSize) );
621 void OutputDevice::DrawDeviceAlphaBitmap( const Bitmap& rBmp, const AlphaMask& rAlpha,
622 const Point& rDestPt, const Size& rDestSize,
623 const Point& rSrcPtPixel, const Size& rSrcSizePixel )
625 assert(!is_double_buffered_window());
627 Point aOutPt(LogicToPixel(rDestPt));
628 Size aOutSz(LogicToPixel(rDestSize));
629 tools::Rectangle aDstRect(Point(), GetOutputSizePixel());
631 const bool bHMirr = aOutSz.Width() < 0;
632 const bool bVMirr = aOutSz.Height() < 0;
634 ClipToPaintRegion(aDstRect);
636 BmpMirrorFlags mirrorFlags = BmpMirrorFlags::NONE;
637 if (bHMirr)
639 aOutSz.setWidth( -aOutSz.Width() );
640 aOutPt.AdjustX( -(aOutSz.Width() - 1) );
641 mirrorFlags |= BmpMirrorFlags::Horizontal;
644 if (bVMirr)
646 aOutSz.setHeight( -aOutSz.Height() );
647 aOutPt.AdjustY( -(aOutSz.Height() - 1) );
648 mirrorFlags |= BmpMirrorFlags::Vertical;
651 if (aDstRect.Intersection(tools::Rectangle(aOutPt, aOutSz)).IsEmpty())
652 return;
654 static const char* pDisableNative = getenv( "SAL_DISABLE_NATIVE_ALPHA");
655 bool bTryDirectPaint = !pDisableNative;
657 if (bTryDirectPaint)
659 Point aRelPt = aOutPt + Point(mnOutOffX, mnOutOffY);
660 SalTwoRect aTR(
661 rSrcPtPixel.X(), rSrcPtPixel.Y(),
662 rSrcSizePixel.Width(), rSrcSizePixel.Height(),
663 aRelPt.X(), aRelPt.Y(),
664 aOutSz.Width(), aOutSz.Height());
666 Bitmap bitmap(rBmp);
667 AlphaMask alpha(rAlpha);
668 if(bHMirr || bVMirr)
670 bitmap.Mirror(mirrorFlags);
671 alpha.Mirror(mirrorFlags);
673 SalBitmap* pSalSrcBmp = bitmap.ImplGetSalBitmap().get();
674 SalBitmap* pSalAlphaBmp = alpha.ImplGetSalBitmap().get();
676 // #i83087# Naturally, system alpha blending (SalGraphics::DrawAlphaBitmap) cannot work
677 // with separate alpha VDev
679 // try to blend the alpha bitmap with the alpha virtual device
680 if (mpAlphaVDev)
682 Bitmap aAlphaBitmap( mpAlphaVDev->GetBitmap( aRelPt, aOutSz ) );
683 if (SalBitmap* pSalAlphaBmp2 = aAlphaBitmap.ImplGetSalBitmap().get())
685 if (mpGraphics->BlendAlphaBitmap(aTR, *pSalSrcBmp, *pSalAlphaBmp, *pSalAlphaBmp2, this))
687 mpAlphaVDev->BlendBitmap(aTR, rAlpha);
688 return;
692 else
694 if (mpGraphics->DrawAlphaBitmap(aTR, *pSalSrcBmp, *pSalAlphaBmp, this))
695 return;
698 // we need to make sure OpenGL never reaches this slow code path
699 #if HAVE_FEATURE_OPENGL
700 assert(!OpenGLHelper::isVCLOpenGLEnabled());
701 #endif
702 assert(!SkiaHelper::isVCLSkiaEnabled());
705 tools::Rectangle aBmpRect(Point(), rBmp.GetSizePixel());
706 if (!aBmpRect.Intersection(tools::Rectangle(rSrcPtPixel, rSrcSizePixel)).IsEmpty())
708 Point auxOutPt(LogicToPixel(rDestPt));
709 Size auxOutSz(LogicToPixel(rDestSize));
711 // HACK: The function is broken with alpha vdev and mirroring, mirror here.
712 Bitmap bitmap(rBmp);
713 AlphaMask alpha(rAlpha);
714 if(mpAlphaVDev && (bHMirr || bVMirr))
716 bitmap.Mirror(mirrorFlags);
717 alpha.Mirror(mirrorFlags);
718 auxOutPt = aOutPt;
719 auxOutSz = aOutSz;
721 DrawDeviceAlphaBitmapSlowPath(bitmap, alpha, aDstRect, aBmpRect, auxOutSz, auxOutPt);
725 namespace
728 struct LinearScaleContext
730 std::unique_ptr<tools::Long[]> mpMapX;
731 std::unique_ptr<tools::Long[]> mpMapY;
733 std::unique_ptr<tools::Long[]> mpMapXOffset;
734 std::unique_ptr<tools::Long[]> mpMapYOffset;
736 LinearScaleContext(tools::Rectangle const & aDstRect, tools::Rectangle const & aBitmapRect,
737 Size const & aOutSize, tools::Long nOffX, tools::Long nOffY)
739 : mpMapX(new tools::Long[aDstRect.GetWidth()])
740 , mpMapY(new tools::Long[aDstRect.GetHeight()])
741 , mpMapXOffset(new tools::Long[aDstRect.GetWidth()])
742 , mpMapYOffset(new tools::Long[aDstRect.GetHeight()])
744 const tools::Long nSrcWidth = aBitmapRect.GetWidth();
745 const tools::Long nSrcHeight = aBitmapRect.GetHeight();
747 generateSimpleMap(
748 nSrcWidth, aDstRect.GetWidth(), aBitmapRect.Left(),
749 aOutSize.Width(), nOffX, mpMapX.get(), mpMapXOffset.get());
751 generateSimpleMap(
752 nSrcHeight, aDstRect.GetHeight(), aBitmapRect.Top(),
753 aOutSize.Height(), nOffY, mpMapY.get(), mpMapYOffset.get());
756 private:
758 static void generateSimpleMap(tools::Long nSrcDimension, tools::Long nDstDimension, tools::Long nDstLocation,
759 tools::Long nOutDimension, tools::Long nOffset, tools::Long* pMap, tools::Long* pMapOffset)
762 const double fReverseScale = (std::abs(nOutDimension) > 1) ? (nSrcDimension - 1) / double(std::abs(nOutDimension) - 1) : 0.0;
764 tools::Long nSampleRange = std::max(tools::Long(0), nSrcDimension - 2);
766 for (tools::Long i = 0; i < nDstDimension; i++)
768 double fTemp = std::abs((nOffset + i) * fReverseScale);
770 pMap[i] = MinMax(nDstLocation + tools::Long(fTemp), 0, nSampleRange);
771 pMapOffset[i] = static_cast<tools::Long>((fTemp - pMap[i]) * 128.0);
775 public:
776 bool blendBitmap(
777 const BitmapWriteAccess* pDestination,
778 const BitmapReadAccess* pSource,
779 const BitmapReadAccess* pSourceAlpha,
780 const tools::Long nDstWidth,
781 const tools::Long nDstHeight)
783 if (pSource && pSourceAlpha && pDestination)
785 ScanlineFormat nSourceFormat = pSource->GetScanlineFormat();
786 ScanlineFormat nDestinationFormat = pDestination->GetScanlineFormat();
788 switch (nSourceFormat)
790 case ScanlineFormat::N24BitTcRgb:
791 case ScanlineFormat::N24BitTcBgr:
793 if ( (nSourceFormat == ScanlineFormat::N24BitTcBgr && nDestinationFormat == ScanlineFormat::N32BitTcBgra)
794 || (nSourceFormat == ScanlineFormat::N24BitTcRgb && nDestinationFormat == ScanlineFormat::N32BitTcRgba))
796 blendBitmap24(pDestination, pSource, pSourceAlpha, nDstWidth, nDstHeight);
797 return true;
800 break;
801 default: break;
804 return false;
807 void blendBitmap24(
808 const BitmapWriteAccess* pDestination,
809 const BitmapReadAccess* pSource,
810 const BitmapReadAccess* pSourceAlpha,
811 const tools::Long nDstWidth,
812 const tools::Long nDstHeight)
814 Scanline pLine0, pLine1;
815 Scanline pLineAlpha0, pLineAlpha1;
816 Scanline pColorSample1, pColorSample2;
817 Scanline pDestScanline;
819 tools::Long nColor1Line1, nColor2Line1, nColor3Line1;
820 tools::Long nColor1Line2, nColor2Line2, nColor3Line2;
821 tools::Long nAlphaLine1, nAlphaLine2;
823 sal_uInt8 nColor1, nColor2, nColor3, nAlpha;
825 for (tools::Long nY = 0; nY < nDstHeight; nY++)
827 const tools::Long nMapY = mpMapY[nY];
828 const tools::Long nMapFY = mpMapYOffset[nY];
830 pLine0 = pSource->GetScanline(nMapY);
831 // tdf#95481 guard nMapY + 1 to be within bounds
832 pLine1 = (nMapY + 1 < pSource->Height()) ? pSource->GetScanline(nMapY + 1) : pLine0;
834 pLineAlpha0 = pSourceAlpha->GetScanline(nMapY);
835 // tdf#95481 guard nMapY + 1 to be within bounds
836 pLineAlpha1 = (nMapY + 1 < pSourceAlpha->Height()) ? pSourceAlpha->GetScanline(nMapY + 1) : pLineAlpha0;
838 pDestScanline = pDestination->GetScanline(nY);
840 for (tools::Long nX = 0; nX < nDstWidth; nX++)
842 const tools::Long nMapX = mpMapX[nX];
843 const tools::Long nMapFX = mpMapXOffset[nX];
845 pColorSample1 = pLine0 + 3 * nMapX;
846 pColorSample2 = (nMapX + 1 < pSource->Width()) ? pColorSample1 + 3 : pColorSample1;
847 nColor1Line1 = (static_cast<tools::Long>(*pColorSample1) << 7) + nMapFX * (static_cast<tools::Long>(*pColorSample2) - *pColorSample1);
849 pColorSample1++;
850 pColorSample2++;
851 nColor2Line1 = (static_cast<tools::Long>(*pColorSample1) << 7) + nMapFX * (static_cast<tools::Long>(*pColorSample2) - *pColorSample1);
853 pColorSample1++;
854 pColorSample2++;
855 nColor3Line1 = (static_cast<tools::Long>(*pColorSample1) << 7) + nMapFX * (static_cast<tools::Long>(*pColorSample2) - *pColorSample1);
857 pColorSample1 = pLine1 + 3 * nMapX;
858 pColorSample2 = (nMapX + 1 < pSource->Width()) ? pColorSample1 + 3 : pColorSample1;
859 nColor1Line2 = (static_cast<tools::Long>(*pColorSample1) << 7) + nMapFX * (static_cast<tools::Long>(*pColorSample2) - *pColorSample1);
861 pColorSample1++;
862 pColorSample2++;
863 nColor2Line2 = (static_cast<tools::Long>(*pColorSample1) << 7) + nMapFX * (static_cast<tools::Long>(*pColorSample2) - *pColorSample1);
865 pColorSample1++;
866 pColorSample2++;
867 nColor3Line2 = (static_cast<tools::Long>(*pColorSample1) << 7) + nMapFX * (static_cast<tools::Long>(*pColorSample2) - *pColorSample1);
869 pColorSample1 = pLineAlpha0 + nMapX;
870 pColorSample2 = (nMapX + 1 < pSourceAlpha->Width()) ? pColorSample1 + 1 : pColorSample1;
871 nAlphaLine1 = (static_cast<tools::Long>(*pColorSample1) << 7) + nMapFX * (static_cast<tools::Long>(*pColorSample2) - *pColorSample1);
873 pColorSample1 = pLineAlpha1 + nMapX;
874 pColorSample2 = (nMapX + 1 < pSourceAlpha->Width()) ? pColorSample1 + 1 : pColorSample1;
875 nAlphaLine2 = (static_cast<tools::Long>(*pColorSample1) << 7) + nMapFX * (static_cast<tools::Long>(*pColorSample2) - *pColorSample1);
877 nColor1 = (nColor1Line1 + nMapFY * ((nColor1Line2 >> 7) - (nColor1Line1 >> 7))) >> 7;
878 nColor2 = (nColor2Line1 + nMapFY * ((nColor2Line2 >> 7) - (nColor2Line1 >> 7))) >> 7;
879 nColor3 = (nColor3Line1 + nMapFY * ((nColor3Line2 >> 7) - (nColor3Line1 >> 7))) >> 7;
881 nAlpha = (nAlphaLine1 + nMapFY * ((nAlphaLine2 >> 7) - (nAlphaLine1 >> 7))) >> 7;
883 *pDestScanline = color::ColorChannelMerge(*pDestScanline, nColor1, nAlpha);
884 pDestScanline++;
885 *pDestScanline = color::ColorChannelMerge(*pDestScanline, nColor2, nAlpha);
886 pDestScanline++;
887 *pDestScanline = color::ColorChannelMerge(*pDestScanline, nColor3, nAlpha);
888 pDestScanline++;
889 pDestScanline++;
895 struct TradScaleContext
897 std::unique_ptr<tools::Long[]> mpMapX;
898 std::unique_ptr<tools::Long[]> mpMapY;
900 TradScaleContext(tools::Rectangle const & aDstRect, tools::Rectangle const & aBitmapRect,
901 Size const & aOutSize, tools::Long nOffX, tools::Long nOffY)
903 : mpMapX(new tools::Long[aDstRect.GetWidth()])
904 , mpMapY(new tools::Long[aDstRect.GetHeight()])
906 const tools::Long nSrcWidth = aBitmapRect.GetWidth();
907 const tools::Long nSrcHeight = aBitmapRect.GetHeight();
909 const bool bHMirr = aOutSize.Width() < 0;
910 const bool bVMirr = aOutSize.Height() < 0;
912 generateSimpleMap(
913 nSrcWidth, aDstRect.GetWidth(), aBitmapRect.Left(),
914 aOutSize.Width(), nOffX, bHMirr, mpMapX.get());
916 generateSimpleMap(
917 nSrcHeight, aDstRect.GetHeight(), aBitmapRect.Top(),
918 aOutSize.Height(), nOffY, bVMirr, mpMapY.get());
921 private:
923 static void generateSimpleMap(tools::Long nSrcDimension, tools::Long nDstDimension, tools::Long nDstLocation,
924 tools::Long nOutDimension, tools::Long nOffset, bool bMirror, tools::Long* pMap)
926 tools::Long nMirrorOffset = 0;
928 if (bMirror)
929 nMirrorOffset = (nDstLocation << 1) + nSrcDimension - 1;
931 for (tools::Long i = 0; i < nDstDimension; ++i, ++nOffset)
933 pMap[i] = nDstLocation + nOffset * nSrcDimension / nOutDimension;
934 if (bMirror)
935 pMap[i] = nMirrorOffset - pMap[i];
941 } // end anonymous namespace
943 void OutputDevice::DrawDeviceAlphaBitmapSlowPath(const Bitmap& rBitmap,
944 const AlphaMask& rAlpha, tools::Rectangle aDstRect, tools::Rectangle aBmpRect, Size const & aOutSize, Point const & aOutPoint)
946 assert(!is_double_buffered_window());
948 VirtualDevice* pOldVDev = mpAlphaVDev;
950 const bool bHMirr = aOutSize.Width() < 0;
951 const bool bVMirr = aOutSize.Height() < 0;
953 // The scaling in this code path produces really ugly results - it
954 // does the most trivial scaling with no smoothing.
955 GDIMetaFile* pOldMetaFile = mpMetaFile;
956 const bool bOldMap = mbMap;
958 mpMetaFile = nullptr; // fdo#55044 reset before GetBitmap!
959 mbMap = false;
961 Bitmap aBmp(GetBitmap(aDstRect.TopLeft(), aDstRect.GetSize()));
963 // #109044# The generated bitmap need not necessarily be
964 // of aDstRect dimensions, it's internally clipped to
965 // window bounds. Thus, we correct the dest size here,
966 // since we later use it (in nDstWidth/Height) for pixel
967 // access)
968 // #i38887# reading from screen may sometimes fail
969 if (aBmp.ImplGetSalBitmap())
971 aDstRect.SetSize(aBmp.GetSizePixel());
974 const tools::Long nDstWidth = aDstRect.GetWidth();
975 const tools::Long nDstHeight = aDstRect.GetHeight();
977 // calculate offset in original bitmap
978 // in RTL case this is a little more complicated since the contents of the
979 // bitmap is not mirrored (it never is), however the paint region and bmp region
980 // are in mirrored coordinates, so the intersection of (aOutPt,aOutSz) with these
981 // is content wise somewhere else and needs to take mirroring into account
982 const tools::Long nOffX = IsRTLEnabled()
983 ? aOutSize.Width() - aDstRect.GetWidth() - (aDstRect.Left() - aOutPoint.X())
984 : aDstRect.Left() - aOutPoint.X();
986 const tools::Long nOffY = aDstRect.Top() - aOutPoint.Y();
988 TradScaleContext aTradContext(aDstRect, aBmpRect, aOutSize, nOffX, nOffY);
990 Bitmap::ScopedReadAccess pBitmapReadAccess(const_cast<Bitmap&>(rBitmap));
991 AlphaMask::ScopedReadAccess pAlphaReadAccess(const_cast<AlphaMask&>(rAlpha));
993 DBG_ASSERT( pAlphaReadAccess->GetScanlineFormat() == ScanlineFormat::N8BitPal,
994 "OutputDevice::ImplDrawAlpha(): non-8bit alpha no longer supported!" );
996 // #i38887# reading from screen may sometimes fail
997 if (aBmp.ImplGetSalBitmap())
999 Bitmap aNewBitmap;
1001 if (mpAlphaVDev)
1003 aNewBitmap = BlendBitmapWithAlpha(
1004 aBmp, pBitmapReadAccess.get(), pAlphaReadAccess.get(),
1005 aDstRect,
1006 nOffY, nDstHeight,
1007 nOffX, nDstWidth,
1008 aTradContext.mpMapX.get(), aTradContext.mpMapY.get() );
1010 else
1012 LinearScaleContext aLinearContext(aDstRect, aBmpRect, aOutSize, nOffX, nOffY);
1014 if (aLinearContext.blendBitmap( BitmapScopedWriteAccess(aBmp).get(), pBitmapReadAccess.get(), pAlphaReadAccess.get(),
1015 nDstWidth, nDstHeight))
1017 aNewBitmap = aBmp;
1019 else
1021 aNewBitmap = BlendBitmap(
1022 aBmp, pBitmapReadAccess.get(), pAlphaReadAccess.get(),
1023 nOffY, nDstHeight,
1024 nOffX, nDstWidth,
1025 aBmpRect, aOutSize,
1026 bHMirr, bVMirr,
1027 aTradContext.mpMapX.get(), aTradContext.mpMapY.get() );
1031 // #110958# Disable alpha VDev, we're doing the necessary
1032 // stuff explicitly further below
1033 if (mpAlphaVDev)
1034 mpAlphaVDev = nullptr;
1036 DrawBitmap(aDstRect.TopLeft(), aNewBitmap);
1038 // #110958# Enable alpha VDev again
1039 mpAlphaVDev = pOldVDev;
1042 mbMap = bOldMap;
1043 mpMetaFile = pOldMetaFile;
1046 void OutputDevice::ScaleBitmap (Bitmap &rBmp, SalTwoRect &rPosAry)
1048 const double nScaleX = rPosAry.mnDestWidth / static_cast<double>( rPosAry.mnSrcWidth );
1049 const double nScaleY = rPosAry.mnDestHeight / static_cast<double>( rPosAry.mnSrcHeight );
1051 // If subsampling, use Bitmap::Scale for subsampling for better quality.
1052 if ( nScaleX < 1.0 || nScaleY < 1.0 )
1054 rBmp.Scale ( nScaleX, nScaleY );
1055 rPosAry.mnSrcWidth = rPosAry.mnDestWidth;
1056 rPosAry.mnSrcHeight = rPosAry.mnDestHeight;
1060 bool OutputDevice::DrawTransformBitmapExDirect(
1061 const basegfx::B2DHomMatrix& aFullTransform,
1062 const BitmapEx& rBitmapEx)
1064 assert(!is_double_buffered_window());
1066 bool bDone = false;
1068 // try to paint directly
1069 const basegfx::B2DPoint aNull(aFullTransform * basegfx::B2DPoint(0.0, 0.0));
1070 const basegfx::B2DPoint aTopX(aFullTransform * basegfx::B2DPoint(1.0, 0.0));
1071 const basegfx::B2DPoint aTopY(aFullTransform * basegfx::B2DPoint(0.0, 1.0));
1072 SalBitmap* pSalSrcBmp = rBitmapEx.GetBitmap().ImplGetSalBitmap().get();
1073 Bitmap aAlphaBitmap;
1075 if(rBitmapEx.IsTransparent())
1077 if(rBitmapEx.IsAlpha())
1079 aAlphaBitmap = rBitmapEx.GetAlpha();
1081 else
1083 aAlphaBitmap = rBitmapEx.GetMask();
1086 else if (mpAlphaVDev)
1088 aAlphaBitmap = AlphaMask(rBitmapEx.GetSizePixel());
1089 aAlphaBitmap.Erase(COL_BLACK); // opaque
1092 SalBitmap* pSalAlphaBmp = aAlphaBitmap.ImplGetSalBitmap().get();
1094 bDone = mpGraphics->DrawTransformedBitmap(
1095 aNull,
1096 aTopX,
1097 aTopY,
1098 *pSalSrcBmp,
1099 pSalAlphaBmp,
1100 this);
1102 if (mpAlphaVDev)
1104 // Merge bitmap alpha to alpha device
1105 AlphaMask aBlack(rBitmapEx.GetSizePixel());
1106 aBlack.Erase(0); // opaque
1107 mpAlphaVDev->DrawTransformBitmapExDirect(aFullTransform, BitmapEx(aBlack, aAlphaBitmap));
1110 return bDone;
1113 bool OutputDevice::TransformAndReduceBitmapExToTargetRange(
1114 const basegfx::B2DHomMatrix& aFullTransform,
1115 basegfx::B2DRange &aVisibleRange,
1116 double &fMaximumArea)
1118 // limit TargetRange to existing pixels (if pixel device)
1119 // first get discrete range of object
1120 basegfx::B2DRange aFullPixelRange(aVisibleRange);
1122 aFullPixelRange.transform(aFullTransform);
1124 if(basegfx::fTools::equalZero(aFullPixelRange.getWidth()) || basegfx::fTools::equalZero(aFullPixelRange.getHeight()))
1126 // object is outside of visible area
1127 return false;
1130 // now get discrete target pixels; start with OutDev pixel size and evtl.
1131 // intersect with active clipping area
1132 basegfx::B2DRange aOutPixel(
1133 0.0,
1134 0.0,
1135 GetOutputSizePixel().Width(),
1136 GetOutputSizePixel().Height());
1138 if(IsClipRegion())
1140 tools::Rectangle aRegionRectangle(GetActiveClipRegion().GetBoundRect());
1142 // caution! Range from rectangle, one too much (!)
1143 aRegionRectangle.AdjustRight(-1);
1144 aRegionRectangle.AdjustBottom(-1);
1145 aOutPixel.intersect( vcl::unotools::b2DRectangleFromRectangle(aRegionRectangle) );
1148 if(aOutPixel.isEmpty())
1150 // no active output area
1151 return false;
1154 // if aFullPixelRange is not completely inside of aOutPixel,
1155 // reduction of target pixels is possible
1156 basegfx::B2DRange aVisiblePixelRange(aFullPixelRange);
1158 if(!aOutPixel.isInside(aFullPixelRange))
1160 aVisiblePixelRange.intersect(aOutPixel);
1162 if(aVisiblePixelRange.isEmpty())
1164 // nothing in visible part, reduces to nothing
1165 return false;
1168 // aVisiblePixelRange contains the reduced output area in
1169 // discrete coordinates. To make it useful everywhere, make it relative to
1170 // the object range
1171 basegfx::B2DHomMatrix aMakeVisibleRangeRelative;
1173 aVisibleRange = aVisiblePixelRange;
1174 aMakeVisibleRangeRelative.translate(
1175 -aFullPixelRange.getMinX(),
1176 -aFullPixelRange.getMinY());
1177 aMakeVisibleRangeRelative.scale(
1178 1.0 / aFullPixelRange.getWidth(),
1179 1.0 / aFullPixelRange.getHeight());
1180 aVisibleRange.transform(aMakeVisibleRangeRelative);
1183 // for pixel devices, do *not* limit size, else OutputDevice::DrawDeviceAlphaBitmap
1184 // will create another, badly scaled bitmap to do the job. Nonetheless, do a
1185 // maximum clipping of something big (1600x1280x2). Add 1.0 to avoid rounding
1186 // errors in rough estimations
1187 const double fNewMaxArea(aVisiblePixelRange.getWidth() * aVisiblePixelRange.getHeight());
1189 fMaximumArea = std::min(4096000.0, fNewMaxArea + 1.0);
1191 return true;
1194 // MM02 add some test class to get a simple timer-based output to be able
1195 // to check if it gets faster - and how much. Uncomment next line or set
1196 // DO_TIME_TEST for compile time if you want to use it
1197 // #define DO_TIME_TEST
1198 #ifdef DO_TIME_TEST
1199 #include <tools/time.hxx>
1200 struct LocalTimeTest
1202 const sal_uInt64 nStartTime;
1203 LocalTimeTest() : nStartTime(tools::Time::GetSystemTicks()) {}
1204 ~LocalTimeTest()
1206 const sal_uInt64 nEndTime(tools::Time::GetSystemTicks());
1207 const sal_uInt64 nDiffTime(nEndTime - nStartTime);
1209 if(nDiffTime > 0)
1211 OStringBuffer aOutput("Time: ");
1212 OString aNumber(OString::number(nDiffTime));
1213 aOutput.append(aNumber);
1214 OSL_FAIL(aOutput.getStr());
1218 #endif
1220 void OutputDevice::DrawTransformedBitmapEx(
1221 const basegfx::B2DHomMatrix& rTransformation,
1222 const BitmapEx& rBitmapEx)
1224 assert(!is_double_buffered_window());
1226 if( ImplIsRecordLayout() )
1227 return;
1229 if(rBitmapEx.IsEmpty())
1230 return;
1232 // MM02 compared to other public methods of OutputDevice
1233 // this test was missing and led to zero-ptr-accesses
1234 if ( !mpGraphics && !AcquireGraphics() )
1235 return;
1237 if ( mbInitClipRegion )
1238 InitClipRegion();
1240 const bool bMetafile(nullptr != mpMetaFile);
1242 tdf#135325 typically in these OutputDevice methods, for the in
1243 record-to-metafile case the MetaFile is already written to before the
1244 test against mbOutputClipped to determine that output to the current
1245 device would result in no visual output. In this case the metafile is
1246 written after the test, so we must continue past mbOutputClipped if
1247 recording to a metafile. It's typical to record with a device of nominal
1248 size and play back later against something of a totally different size.
1250 if (mbOutputClipped && !bMetafile)
1251 return;
1253 #ifdef DO_TIME_TEST
1254 // MM02 start time test when some data (not for trivial stuff). Will
1255 // trigger and show data when leaving this method by destructing helper
1256 static const char* pEnableBitmapDrawTimerTimer(getenv("SAL_ENABLE_TIMER_BITMAPDRAW"));
1257 static bool bUseTimer(nullptr != pEnableBitmapDrawTimerTimer);
1258 std::unique_ptr<LocalTimeTest> aTimeTest(
1259 bUseTimer && rBitmapEx.GetSizeBytes() > 10000
1260 ? new LocalTimeTest()
1261 : nullptr);
1262 #endif
1264 // decompose matrix to check rotation and shear
1265 basegfx::B2DVector aScale, aTranslate;
1266 double fRotate, fShearX;
1267 rTransformation.decompose(aScale, aTranslate, fRotate, fShearX);
1268 const bool bRotated(!basegfx::fTools::equalZero(fRotate));
1269 const bool bSheared(!basegfx::fTools::equalZero(fShearX));
1270 const bool bMirroredX(basegfx::fTools::less(aScale.getX(), 0.0));
1271 const bool bMirroredY(basegfx::fTools::less(aScale.getY(), 0.0));
1273 if(!bRotated && !bSheared && !bMirroredX && !bMirroredY)
1275 // with no rotation, shear or mirroring it can be mapped to DrawBitmapEx
1276 // do *not* execute the mirroring here, it's done in the fallback
1277 // #i124580# the correct DestSize needs to be calculated based on MaxXY values
1278 Point aDestPt(basegfx::fround(aTranslate.getX()), basegfx::fround(aTranslate.getY()));
1279 const Size aDestSize(
1280 basegfx::fround(aScale.getX() + aTranslate.getX()) - aDestPt.X(),
1281 basegfx::fround(aScale.getY() + aTranslate.getY()) - aDestPt.Y());
1282 const Point aOrigin = GetMapMode().GetOrigin();
1283 if (!bMetafile && comphelper::LibreOfficeKit::isActive() && GetMapMode().GetMapUnit() != MapUnit::MapPixel)
1285 aDestPt.Move(aOrigin.getX(), aOrigin.getY());
1286 EnableMapMode(false);
1289 DrawBitmapEx(aDestPt, aDestSize, rBitmapEx);
1290 if (!bMetafile && comphelper::LibreOfficeKit::isActive() && GetMapMode().GetMapUnit() != MapUnit::MapPixel)
1292 EnableMapMode();
1293 aDestPt.Move(-aOrigin.getX(), -aOrigin.getY());
1295 return;
1298 const bool bInvert(RasterOp::Invert == meRasterOp);
1299 const bool bBitmapChangedColor(mnDrawMode & (DrawModeFlags::BlackBitmap | DrawModeFlags::WhiteBitmap | DrawModeFlags::GrayBitmap ));
1300 const bool bTryDirectPaint(!bInvert && !bBitmapChangedColor && !bMetafile);
1301 if(bTryDirectPaint)
1303 // tdf#130768 CAUTION(!) using GetViewTransformation() is *not* enough here, it may
1304 // be that mnOutOffX/mnOutOffY is used - see AOO bug 75163, mentioned at
1305 // ImplGetDeviceTransformation declaration
1306 const basegfx::B2DHomMatrix aFullTransform(ImplGetDeviceTransformation() * rTransformation);
1308 if(DrawTransformBitmapExDirect(aFullTransform, rBitmapEx))
1310 // we are done
1311 return;
1315 // take the fallback when no rotate and shear, but mirror (else we would have done this above)
1316 if(!bRotated && !bSheared)
1318 // with no rotation or shear it can be mapped to DrawBitmapEx
1319 // do *not* execute the mirroring here, it's done in the fallback
1320 // #i124580# the correct DestSize needs to be calculated based on MaxXY values
1321 const Point aDestPt(basegfx::fround(aTranslate.getX()), basegfx::fround(aTranslate.getY()));
1322 const Size aDestSize(
1323 basegfx::fround(aScale.getX() + aTranslate.getX()) - aDestPt.X(),
1324 basegfx::fround(aScale.getY() + aTranslate.getY()) - aDestPt.Y());
1326 DrawBitmapEx(aDestPt, aDestSize, rBitmapEx);
1327 return;
1330 // at this point we are either sheared or rotated or both
1331 assert(bSheared || bRotated);
1333 // fallback; create transformed bitmap the hard way (back-transform
1334 // the pixels) and paint
1335 basegfx::B2DRange aVisibleRange(0.0, 0.0, 1.0, 1.0);
1337 // limit maximum area to something looking good for non-pixel-based targets (metafile, printer)
1338 // by using a fixed minimum (allow at least, but no need to utilize) for good smoothing and an area
1339 // dependent of original size for good quality when e.g. rotated/sheared. Still, limit to a maximum
1340 // to avoid crashes/resource problems (ca. 1500x3000 here)
1341 const Size& rOriginalSizePixel(rBitmapEx.GetSizePixel());
1342 const double fOrigArea(rOriginalSizePixel.Width() * rOriginalSizePixel.Height() * 0.5);
1343 const double fOrigAreaScaled(fOrigArea * 1.44);
1344 double fMaximumArea(std::clamp(fOrigAreaScaled, 1000000.0, 4500000.0));
1345 // tdf#130768 CAUTION(!) using GetViewTransformation() is *not* enough here, it may
1346 // be that mnOutOffX/mnOutOffY is used - see AOO bug 75163, mentioned at
1347 // ImplGetDeviceTransformation declaration
1348 basegfx::B2DHomMatrix aFullTransform(ImplGetDeviceTransformation() * rTransformation);
1350 if(!bMetafile)
1352 if ( !TransformAndReduceBitmapExToTargetRange( aFullTransform, aVisibleRange, fMaximumArea ) )
1353 return;
1356 if(aVisibleRange.isEmpty())
1357 return;
1359 BitmapEx aTransformed(rBitmapEx);
1361 // #122923# when the result needs an alpha channel due to being rotated or sheared
1362 // and thus uncovering areas, add these channels so that the own transformer (used
1363 // in getTransformed) also creates a transformed alpha channel
1364 if(!aTransformed.IsTransparent() && (bSheared || bRotated))
1366 // parts will be uncovered, extend aTransformed with a mask bitmap
1367 const Bitmap aContent(aTransformed.GetBitmap());
1369 AlphaMask aMaskBmp(aContent.GetSizePixel());
1370 aMaskBmp.Erase(0);
1372 aTransformed = BitmapEx(aContent, aMaskBmp);
1375 // Remove scaling from aFulltransform: we transform due to shearing or rotation, scaling
1376 // will happen according to aDestSize.
1377 basegfx::B2DVector aFullScale, aFullTranslate;
1378 double fFullRotate, fFullShearX;
1379 aFullTransform.decompose(aFullScale, aFullTranslate, fFullRotate, fFullShearX);
1380 // Require positive scaling, negative scaling would loose horizontal or vertical flip.
1381 if (aFullScale.getX() > 0 && aFullScale.getY() > 0)
1383 basegfx::B2DHomMatrix aTransform = basegfx::utils::createScaleB2DHomMatrix(
1384 rOriginalSizePixel.getWidth() / aFullScale.getX(),
1385 rOriginalSizePixel.getHeight() / aFullScale.getY());
1386 aFullTransform *= aTransform;
1389 double fSourceRatio = 1.0;
1390 if (rOriginalSizePixel.getHeight() != 0)
1392 fSourceRatio = rOriginalSizePixel.getWidth() / rOriginalSizePixel.getHeight();
1394 double fTargetRatio = 1.0;
1395 if (aFullScale.getY() != 0)
1397 fTargetRatio = aFullScale.getX() / aFullScale.getY();
1399 bool bAspectRatioKept = rtl::math::approxEqual(fSourceRatio, fTargetRatio);
1400 if (bSheared || !bAspectRatioKept)
1402 // Not only rotation, or scaling does not keep aspect ratio.
1403 aTransformed = aTransformed.getTransformed(
1404 aFullTransform,
1405 aVisibleRange,
1406 fMaximumArea);
1408 else
1410 // Just rotation, can do that directly.
1411 fFullRotate = fmod(fFullRotate * -1, F_2PI);
1412 if (fFullRotate < 0)
1414 fFullRotate += F_2PI;
1416 Degree10 nAngle10(basegfx::fround(basegfx::rad2deg(fFullRotate) * 10));
1417 aTransformed.Rotate(nAngle10, COL_TRANSPARENT);
1419 basegfx::B2DRange aTargetRange(0.0, 0.0, 1.0, 1.0);
1421 // get logic object target range
1422 aTargetRange.transform(rTransformation);
1424 // get from unified/relative VisibleRange to logoc one
1425 aVisibleRange.transform(
1426 basegfx::utils::createScaleTranslateB2DHomMatrix(
1427 aTargetRange.getRange(),
1428 aTargetRange.getMinimum()));
1430 // extract point and size; do not remove size, the bitmap may have been prepared reduced by purpose
1431 // #i124580# the correct DestSize needs to be calculated based on MaxXY values
1432 const Point aDestPt(basegfx::fround(aVisibleRange.getMinX()), basegfx::fround(aVisibleRange.getMinY()));
1433 const Size aDestSize(
1434 basegfx::fround(aVisibleRange.getMaxX()) - aDestPt.X(),
1435 basegfx::fround(aVisibleRange.getMaxY()) - aDestPt.Y());
1437 DrawBitmapEx(aDestPt, aDestSize, aTransformed);
1440 void OutputDevice::DrawShadowBitmapEx(
1441 const BitmapEx& rBitmapEx,
1442 ::Color aShadowColor)
1444 Bitmap::ScopedReadAccess pReadAccess(const_cast<Bitmap&>(rBitmapEx.maBitmap));
1446 if(!pReadAccess)
1447 return;
1449 for(tools::Long y(0); y < pReadAccess->Height(); y++)
1451 for(tools::Long x(0); x < pReadAccess->Width(); x++)
1453 const BitmapColor aColor = pReadAccess->GetColor(y, x);
1454 sal_uInt16 nLuminance(static_cast<sal_uInt16>(aColor.GetLuminance()) + 1);
1455 const Color aDestColor(
1456 static_cast<sal_uInt8>((nLuminance * static_cast<sal_uInt16>(aShadowColor.GetRed())) >> 8),
1457 static_cast<sal_uInt8>((nLuminance * static_cast<sal_uInt16>(aShadowColor.GetGreen())) >> 8),
1458 static_cast<sal_uInt8>((nLuminance * static_cast<sal_uInt16>(aShadowColor.GetBlue())) >> 8));
1459 DrawPixel(Point(x,y), aDestColor);
1464 void OutputDevice::DrawImage( const Point& rPos, const Image& rImage, DrawImageFlags nStyle )
1466 assert(!is_double_buffered_window());
1468 DrawImage( rPos, Size(), rImage, nStyle );
1471 void OutputDevice::DrawImage( const Point& rPos, const Size& rSize,
1472 const Image& rImage, DrawImageFlags nStyle )
1474 assert(!is_double_buffered_window());
1476 bool bIsSizeValid = !rSize.IsEmpty();
1478 if (!ImplIsRecordLayout())
1480 Image& rNonConstImage = const_cast<Image&>(rImage);
1481 if (bIsSizeValid)
1482 rNonConstImage.Draw(this, rPos, nStyle, &rSize);
1483 else
1484 rNonConstImage.Draw(this, rPos, nStyle);
1488 namespace
1490 // Co = Cs + Cd*(1-As) premultiplied alpha -or-
1491 // Co = (AsCs + AdCd*(1-As)) / Ao
1492 sal_uInt8 CalcColor( const sal_uInt8 nSourceColor, const sal_uInt8 nSourceAlpha,
1493 const sal_uInt8 nDstAlpha, const sal_uInt8 nResAlpha, const sal_uInt8 nDestColor )
1495 int c = nResAlpha ? ( static_cast<int>(nSourceAlpha)*nSourceColor + static_cast<int>(nDstAlpha)*nDestColor -
1496 static_cast<int>(nDstAlpha)*nDestColor*nSourceAlpha/255 ) / static_cast<int>(nResAlpha) : 0;
1497 return sal_uInt8( c );
1500 BitmapColor AlphaBlend( int nX, int nY,
1501 const tools::Long nMapX,
1502 const tools::Long nMapY,
1503 BitmapReadAccess const * pP,
1504 BitmapReadAccess const * pA,
1505 BitmapReadAccess const * pB,
1506 BitmapWriteAccess const * pAlphaW,
1507 sal_uInt8& nResAlpha )
1509 BitmapColor aDstCol,aSrcCol;
1510 aSrcCol = pP->GetColor( nMapY, nMapX );
1511 aDstCol = pB->GetColor( nY, nX );
1513 // vcl stores transparency, not alpha - invert it
1514 const sal_uInt8 nSrcAlpha = 255 - pA->GetPixelIndex( nMapY, nMapX );
1515 const sal_uInt8 nDstAlpha = 255 - pAlphaW->GetPixelIndex( nY, nX );
1517 // Perform porter-duff compositing 'over' operation
1519 // Co = Cs + Cd*(1-As)
1520 // Ad = As + Ad*(1-As)
1521 nResAlpha = static_cast<int>(nSrcAlpha) + static_cast<int>(nDstAlpha) - static_cast<int>(nDstAlpha)*nSrcAlpha/255;
1523 aDstCol.SetRed( CalcColor( aSrcCol.GetRed(), nSrcAlpha, nDstAlpha, nResAlpha, aDstCol.GetRed() ) );
1524 aDstCol.SetBlue( CalcColor( aSrcCol.GetBlue(), nSrcAlpha, nDstAlpha, nResAlpha, aDstCol.GetBlue() ) );
1525 aDstCol.SetGreen( CalcColor( aSrcCol.GetGreen(), nSrcAlpha, nDstAlpha, nResAlpha, aDstCol.GetGreen() ) );
1527 return aDstCol;
1531 void OutputDevice::BlendBitmap(
1532 const SalTwoRect& rPosAry,
1533 const Bitmap& rBmp )
1535 mpGraphics->BlendBitmap( rPosAry, *rBmp.ImplGetSalBitmap(), this );
1538 Bitmap OutputDevice::BlendBitmapWithAlpha(
1539 Bitmap& aBmp,
1540 BitmapReadAccess const * pP,
1541 BitmapReadAccess const * pA,
1542 const tools::Rectangle& aDstRect,
1543 const sal_Int32 nOffY,
1544 const sal_Int32 nDstHeight,
1545 const sal_Int32 nOffX,
1546 const sal_Int32 nDstWidth,
1547 const tools::Long* pMapX,
1548 const tools::Long* pMapY )
1551 BitmapColor aDstCol;
1552 Bitmap res;
1553 int nX, nY;
1554 sal_uInt8 nResAlpha;
1556 SAL_WARN_IF( !mpAlphaVDev, "vcl.gdi", "BlendBitmapWithAlpha(): call me only with valid alpha VirtualDevice!" );
1558 bool bOldMapMode( mpAlphaVDev->IsMapModeEnabled() );
1559 mpAlphaVDev->EnableMapMode(false);
1561 Bitmap aAlphaBitmap( mpAlphaVDev->GetBitmap( aDstRect.TopLeft(), aDstRect.GetSize() ) );
1562 BitmapScopedWriteAccess pAlphaW(aAlphaBitmap);
1564 if( GetBitCount() <= 8 )
1566 Bitmap aDither( aBmp.GetSizePixel(), 8 );
1567 BitmapColor aIndex( 0 );
1568 Bitmap::ScopedReadAccess pB(aBmp);
1569 BitmapScopedWriteAccess pW(aDither);
1571 if (pB && pP && pA && pW && pAlphaW)
1573 int nOutY;
1575 for( nY = 0, nOutY = nOffY; nY < nDstHeight; nY++, nOutY++ )
1577 const tools::Long nMapY = pMapY[ nY ];
1578 const tools::Long nModY = ( nOutY & 0x0FL ) << 4;
1579 int nOutX;
1581 Scanline pScanline = pW->GetScanline(nY);
1582 Scanline pScanlineAlpha = pAlphaW->GetScanline(nY);
1583 for( nX = 0, nOutX = nOffX; nX < nDstWidth; nX++, nOutX++ )
1585 const tools::Long nMapX = pMapX[ nX ];
1586 const sal_uLong nD = nVCLDitherLut[ nModY | ( nOutX & 0x0FL ) ];
1588 aDstCol = AlphaBlend( nX, nY, nMapX, nMapY, pP, pA, pB.get(), pAlphaW.get(), nResAlpha );
1590 aIndex.SetIndex( static_cast<sal_uInt8>( nVCLRLut[ ( nVCLLut[ aDstCol.GetRed() ] + nD ) >> 16 ] +
1591 nVCLGLut[ ( nVCLLut[ aDstCol.GetGreen() ] + nD ) >> 16 ] +
1592 nVCLBLut[ ( nVCLLut[ aDstCol.GetBlue() ] + nD ) >> 16 ] ) );
1593 pW->SetPixelOnData( pScanline, nX, aIndex );
1595 aIndex.SetIndex( static_cast<sal_uInt8>( nVCLRLut[ ( nVCLLut[ 255-nResAlpha ] + nD ) >> 16 ] +
1596 nVCLGLut[ ( nVCLLut[ 255-nResAlpha ] + nD ) >> 16 ] +
1597 nVCLBLut[ ( nVCLLut[ 255-nResAlpha ] + nD ) >> 16 ] ) );
1598 pAlphaW->SetPixelOnData( pScanlineAlpha, nX, aIndex );
1602 pB.reset();
1603 pW.reset();
1604 res = aDither;
1606 else
1608 BitmapScopedWriteAccess pB(aBmp);
1609 if (pB && pP && pA && pAlphaW)
1611 for( nY = 0; nY < nDstHeight; nY++ )
1613 const tools::Long nMapY = pMapY[ nY ];
1614 Scanline pScanlineB = pB->GetScanline(nY);
1615 Scanline pScanlineAlpha = pAlphaW->GetScanline(nY);
1617 for( nX = 0; nX < nDstWidth; nX++ )
1619 const tools::Long nMapX = pMapX[ nX ];
1620 aDstCol = AlphaBlend( nX, nY, nMapX, nMapY, pP, pA, pB.get(), pAlphaW.get(), nResAlpha );
1622 pB->SetPixelOnData(pScanlineB, nX, pB->GetBestMatchingColor(aDstCol));
1623 pAlphaW->SetPixelOnData(pScanlineAlpha, nX, pB->GetBestMatchingColor(Color(255L-nResAlpha, 255L-nResAlpha, 255L-nResAlpha)));
1627 pB.reset();
1628 res = aBmp;
1631 pAlphaW.reset();
1632 mpAlphaVDev->DrawBitmap( aDstRect.TopLeft(), aAlphaBitmap );
1633 mpAlphaVDev->EnableMapMode( bOldMapMode );
1635 return res;
1638 Bitmap OutputDevice::BlendBitmap(
1639 Bitmap& aBmp,
1640 BitmapReadAccess const * pP,
1641 BitmapReadAccess const * pA,
1642 const sal_Int32 nOffY,
1643 const sal_Int32 nDstHeight,
1644 const sal_Int32 nOffX,
1645 const sal_Int32 nDstWidth,
1646 const tools::Rectangle& aBmpRect,
1647 const Size& aOutSz,
1648 const bool bHMirr,
1649 const bool bVMirr,
1650 const tools::Long* pMapX,
1651 const tools::Long* pMapY )
1653 BitmapColor aDstCol;
1654 Bitmap res;
1655 int nX, nY;
1657 if( GetBitCount() <= 8 )
1659 Bitmap aDither( aBmp.GetSizePixel(), 8 );
1660 BitmapColor aIndex( 0 );
1661 Bitmap::ScopedReadAccess pB(aBmp);
1662 BitmapScopedWriteAccess pW(aDither);
1664 if( pB && pP && pA && pW )
1666 int nOutY;
1668 for( nY = 0, nOutY = nOffY; nY < nDstHeight; nY++, nOutY++ )
1670 tools::Long nMapY = pMapY[ nY ];
1671 if (bVMirr)
1673 nMapY = aBmpRect.Bottom() - nMapY;
1675 const tools::Long nModY = ( nOutY & 0x0FL ) << 4;
1676 int nOutX;
1678 Scanline pScanline = pW->GetScanline(nY);
1679 Scanline pScanlineAlpha = pA->GetScanline(nMapY);
1680 for( nX = 0, nOutX = nOffX; nX < nDstWidth; nX++, nOutX++ )
1682 tools::Long nMapX = pMapX[ nX ];
1683 if (bHMirr)
1685 nMapX = aBmpRect.Right() - nMapX;
1687 const sal_uLong nD = nVCLDitherLut[ nModY | ( nOutX & 0x0FL ) ];
1689 aDstCol = pB->GetColor( nY, nX );
1690 aDstCol.Merge( pP->GetColor( nMapY, nMapX ), pA->GetIndexFromData( pScanlineAlpha, nMapX ) );
1691 aIndex.SetIndex( static_cast<sal_uInt8>( nVCLRLut[ ( nVCLLut[ aDstCol.GetRed() ] + nD ) >> 16 ] +
1692 nVCLGLut[ ( nVCLLut[ aDstCol.GetGreen() ] + nD ) >> 16 ] +
1693 nVCLBLut[ ( nVCLLut[ aDstCol.GetBlue() ] + nD ) >> 16 ] ) );
1694 pW->SetPixelOnData( pScanline, nX, aIndex );
1699 pB.reset();
1700 pW.reset();
1701 res = aDither;
1703 else
1705 BitmapScopedWriteAccess pB(aBmp);
1707 bool bFastBlend = false;
1708 if( pP && pA && pB && !bHMirr && !bVMirr )
1710 SalTwoRect aTR(aBmpRect.Left(), aBmpRect.Top(), aBmpRect.GetWidth(), aBmpRect.GetHeight(),
1711 nOffX, nOffY, aOutSz.Width(), aOutSz.Height());
1713 bFastBlend = ImplFastBitmapBlending( *pB,*pP,*pA, aTR );
1716 if( pP && pA && pB && !bFastBlend )
1718 switch( pP->GetScanlineFormat() )
1720 case ScanlineFormat::N8BitPal:
1722 for( nY = 0; nY < nDstHeight; nY++ )
1724 tools::Long nMapY = pMapY[ nY ];
1725 if ( bVMirr )
1727 nMapY = aBmpRect.Bottom() - nMapY;
1729 Scanline pPScan = pP->GetScanline( nMapY );
1730 Scanline pAScan = pA->GetScanline( nMapY );
1731 Scanline pBScan = pB->GetScanline( nY );
1733 for( nX = 0; nX < nDstWidth; nX++ )
1735 tools::Long nMapX = pMapX[ nX ];
1737 if ( bHMirr )
1739 nMapX = aBmpRect.Right() - nMapX;
1741 aDstCol = pB->GetPixelFromData( pBScan, nX );
1742 aDstCol.Merge( pP->GetPaletteColor( pPScan[ nMapX ] ), pAScan[ nMapX ] );
1743 pB->SetPixelOnData( pBScan, nX, aDstCol );
1747 break;
1749 default:
1752 for( nY = 0; nY < nDstHeight; nY++ )
1754 tools::Long nMapY = pMapY[ nY ];
1756 if ( bVMirr )
1758 nMapY = aBmpRect.Bottom() - nMapY;
1760 Scanline pAScan = pA->GetScanline( nMapY );
1761 Scanline pBScan = pB->GetScanline(nY);
1762 for( nX = 0; nX < nDstWidth; nX++ )
1764 tools::Long nMapX = pMapX[ nX ];
1766 if ( bHMirr )
1768 nMapX = aBmpRect.Right() - nMapX;
1770 aDstCol = pB->GetPixelFromData( pBScan, nX );
1771 aDstCol.Merge( pP->GetColor( nMapY, nMapX ), pAScan[ nMapX ] );
1772 pB->SetPixelOnData( pBScan, nX, aDstCol );
1776 break;
1780 pB.reset();
1781 res = aBmp;
1784 return res;
1787 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */