1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
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 .
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>
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>
44 #include <basegfx/matrix/b2dhommatrixtools.hxx>
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() )
82 if ( RasterOp::Invert
== meRasterOp
)
84 DrawRect( tools::Rectangle( rDestPt
, rDestSize
) );
88 Bitmap
aBmp( rBitmap
);
90 if ( mnDrawMode
& ( DrawModeFlags::BlackBitmap
| DrawModeFlags::WhiteBitmap
|
91 DrawModeFlags::GrayBitmap
) )
93 if ( mnDrawMode
& ( DrawModeFlags::BlackBitmap
| DrawModeFlags::WhiteBitmap
) )
97 if ( mnDrawMode
& DrawModeFlags::BlackBitmap
)
102 Color
aCol( cCmpVal
, cCmpVal
, cCmpVal
);
103 Push( PushFlags::LINECOLOR
| PushFlags::FILLCOLOR
);
104 SetLineColor( aCol
);
105 SetFillColor( aCol
);
106 DrawRect( tools::Rectangle( rDestPt
, rDestSize
) );
112 if ( mnDrawMode
& DrawModeFlags::GrayBitmap
)
113 aBmp
.Convert( BmpConversion::N8BitGreys
);
121 case MetaActionType::BMP
:
122 mpMetaFile
->AddAction( new MetaBmpAction( rDestPt
, aBmp
) );
125 case MetaActionType::BMPSCALE
:
126 mpMetaFile
->AddAction( new MetaBmpScaleAction( rDestPt
, rDestSize
, aBmp
) );
129 case MetaActionType::BMPSCALEPART
:
130 mpMetaFile
->AddAction( new MetaBmpScalePartAction(
131 rDestPt
, rDestSize
, rSrcPtPixel
, rSrcSizePixel
, aBmp
) );
138 if ( !IsDeviceOutputNecessary() )
141 if ( !mpGraphics
&& !AcquireGraphics() )
144 if ( mbInitClipRegion
)
147 if ( mbOutputClipped
)
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 );
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
)
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
);
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 ) )
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
);
247 void OutputDevice::DrawBitmapEx( const Point
& rDestPt
,
248 const BitmapEx
& rBitmapEx
)
250 assert(!is_double_buffered_window());
252 if( ImplIsRecordLayout() )
255 if( TransparentType::NONE
== rBitmapEx
.GetTransparentType() )
257 DrawBitmap( rDestPt
, rBitmapEx
.GetBitmap() );
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() )
274 if ( TransparentType::NONE
== rBitmapEx
.GetTransparentType() )
276 DrawBitmap( rDestPt
, rDestSize
, rBitmapEx
.GetBitmap() );
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() )
294 if( TransparentType::NONE
== rBitmapEx
.GetTransparentType() )
296 DrawBitmap( rDestPt
, rDestSize
, rSrcPtPixel
, rSrcSizePixel
, rBitmapEx
.GetBitmap() );
300 if ( RasterOp::Invert
== meRasterOp
)
302 DrawRect( tools::Rectangle( rDestPt
, rDestSize
) );
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 );
316 if ( mnDrawMode
& DrawModeFlags::BlackBitmap
)
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
330 BitmapEx
aMaskEx(aBmpEx
.GetAlpha().GetBitmap());
331 BitmapFilter::Filter(aMaskEx
, BitmapMonochromeFilter(129));
332 aBmpEx
= BitmapEx(aColorBmp
, aMaskEx
.GetBitmap());
336 aBmpEx
= BitmapEx( aColorBmp
, aBmpEx
.GetMask() );
341 if ( mnDrawMode
& DrawModeFlags::GrayBitmap
)
342 aBmpEx
.Convert( BmpConversion::N8BitGreys
);
350 case MetaActionType::BMPEX
:
351 mpMetaFile
->AddAction( new MetaBmpExAction( rDestPt
, aBmpEx
) );
354 case MetaActionType::BMPEXSCALE
:
355 mpMetaFile
->AddAction( new MetaBmpExScaleAction( rDestPt
, rDestSize
, aBmpEx
) );
358 case MetaActionType::BMPEXSCALEPART
:
359 mpMetaFile
->AddAction( new MetaBmpExScalePartAction( rDestPt
, rDestSize
,
360 rSrcPtPixel
, rSrcSizePixel
, aBmpEx
) );
367 if ( !IsDeviceOutputNecessary() )
370 if ( !mpGraphics
&& !AcquireGraphics() )
373 if ( mbInitClipRegion
)
376 if ( mbOutputClipped
)
379 DrawDeviceBitmap( rDestPt
, rDestSize
, rSrcPtPixel
, rSrcSizePixel
, aBmpEx
);
383 Bitmap
OutputDevice::GetBitmap( const Point
& rSrcPt
, const Size
& rSize
) const
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
);
406 // Y-Coordinate outside of draw area?
407 if ( nY
< mnOutOffY
)
409 nHeight
-= ( mnOutOffY
- nY
);
414 // Width outside of draw area?
415 if ( (nWidth
+ nX
) > (mnOutWidth
+ mnOutOffX
) )
417 nWidth
= mnOutOffX
+ mnOutWidth
- nX
;
421 // Height outside of draw area?
422 if ( (nHeight
+ nY
) > (mnOutHeight
+ mnOutOffY
) )
424 nHeight
= mnOutOffY
+ mnOutHeight
- nY
;
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,
445 aVDev
->mpGraphics
->CopyBits( aPosAry
, mpGraphics
, this, this );
449 OSL_ENSURE(false, "CopyBits with zero or negative width or height");
452 aBmp
= aVDev
->GetBitmap( Point(), aVDev
->GetOutputSizePixel() );
463 std::shared_ptr
<SalBitmap
> pSalBmp
= mpGraphics
->GetBitmap( nX
, nY
, nWidth
, nHeight
, this );
467 aBmp
.ImplSetSalBitmap(pSalBmp
);
476 BitmapEx
OutputDevice::GetBitmapEx( const Point
& rSrcPt
, const Size
& rSize
) const
479 // #110958# Extract alpha value from VDev, if any
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
) );
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();
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
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
,
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
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
602 mpAlphaVDev
->DrawBitmapEx(rDestPt
,
604 BitmapEx(rBitmapEx
.GetMask(),
605 rBitmapEx
.GetMask()));
609 mpGraphics
->DrawBitmap(aPosAry
, *pSalSrcBmp
, this);
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
;
639 aOutSz
.setWidth( -aOutSz
.Width() );
640 aOutPt
.AdjustX( -(aOutSz
.Width() - 1) );
641 mirrorFlags
|= BmpMirrorFlags::Horizontal
;
646 aOutSz
.setHeight( -aOutSz
.Height() );
647 aOutPt
.AdjustY( -(aOutSz
.Height() - 1) );
648 mirrorFlags
|= BmpMirrorFlags::Vertical
;
651 if (aDstRect
.Intersection(tools::Rectangle(aOutPt
, aOutSz
)).IsEmpty())
654 static const char* pDisableNative
= getenv( "SAL_DISABLE_NATIVE_ALPHA");
655 bool bTryDirectPaint
= !pDisableNative
;
659 Point aRelPt
= aOutPt
+ Point(mnOutOffX
, mnOutOffY
);
661 rSrcPtPixel
.X(), rSrcPtPixel
.Y(),
662 rSrcSizePixel
.Width(), rSrcSizePixel
.Height(),
663 aRelPt
.X(), aRelPt
.Y(),
664 aOutSz
.Width(), aOutSz
.Height());
667 AlphaMask
alpha(rAlpha
);
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
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
);
694 if (mpGraphics
->DrawAlphaBitmap(aTR
, *pSalSrcBmp
, *pSalAlphaBmp
, this))
698 // we need to make sure OpenGL never reaches this slow code path
699 #if HAVE_FEATURE_OPENGL
700 assert(!OpenGLHelper::isVCLOpenGLEnabled());
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.
713 AlphaMask
alpha(rAlpha
);
714 if(mpAlphaVDev
&& (bHMirr
|| bVMirr
))
716 bitmap
.Mirror(mirrorFlags
);
717 alpha
.Mirror(mirrorFlags
);
721 DrawDeviceAlphaBitmapSlowPath(bitmap
, alpha
, aDstRect
, aBmpRect
, auxOutSz
, auxOutPt
);
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();
748 nSrcWidth
, aDstRect
.GetWidth(), aBitmapRect
.Left(),
749 aOutSize
.Width(), nOffX
, mpMapX
.get(), mpMapXOffset
.get());
752 nSrcHeight
, aDstRect
.GetHeight(), aBitmapRect
.Top(),
753 aOutSize
.Height(), nOffY
, mpMapY
.get(), mpMapYOffset
.get());
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);
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
);
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
);
851 nColor2Line1
= (static_cast<tools::Long
>(*pColorSample1
) << 7) + nMapFX
* (static_cast<tools::Long
>(*pColorSample2
) - *pColorSample1
);
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
);
863 nColor2Line2
= (static_cast<tools::Long
>(*pColorSample1
) << 7) + nMapFX
* (static_cast<tools::Long
>(*pColorSample2
) - *pColorSample1
);
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
);
885 *pDestScanline
= color::ColorChannelMerge(*pDestScanline
, nColor2
, nAlpha
);
887 *pDestScanline
= color::ColorChannelMerge(*pDestScanline
, nColor3
, nAlpha
);
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;
913 nSrcWidth
, aDstRect
.GetWidth(), aBitmapRect
.Left(),
914 aOutSize
.Width(), nOffX
, bHMirr
, mpMapX
.get());
917 nSrcHeight
, aDstRect
.GetHeight(), aBitmapRect
.Top(),
918 aOutSize
.Height(), nOffY
, bVMirr
, mpMapY
.get());
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;
929 nMirrorOffset
= (nDstLocation
<< 1) + nSrcDimension
- 1;
931 for (tools::Long i
= 0; i
< nDstDimension
; ++i
, ++nOffset
)
933 pMap
[i
] = nDstLocation
+ nOffset
* nSrcDimension
/ nOutDimension
;
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!
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
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())
1003 aNewBitmap
= BlendBitmapWithAlpha(
1004 aBmp
, pBitmapReadAccess
.get(), pAlphaReadAccess
.get(),
1008 aTradContext
.mpMapX
.get(), aTradContext
.mpMapY
.get() );
1012 LinearScaleContext
aLinearContext(aDstRect
, aBmpRect
, aOutSize
, nOffX
, nOffY
);
1014 if (aLinearContext
.blendBitmap( BitmapScopedWriteAccess(aBmp
).get(), pBitmapReadAccess
.get(), pAlphaReadAccess
.get(),
1015 nDstWidth
, nDstHeight
))
1021 aNewBitmap
= BlendBitmap(
1022 aBmp
, pBitmapReadAccess
.get(), pAlphaReadAccess
.get(),
1027 aTradContext
.mpMapX
.get(), aTradContext
.mpMapY
.get() );
1031 // #110958# Disable alpha VDev, we're doing the necessary
1032 // stuff explicitly further below
1034 mpAlphaVDev
= nullptr;
1036 DrawBitmap(aDstRect
.TopLeft(), aNewBitmap
);
1038 // #110958# Enable alpha VDev again
1039 mpAlphaVDev
= pOldVDev
;
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());
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();
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(
1104 // Merge bitmap alpha to alpha device
1105 AlphaMask
aBlack(rBitmapEx
.GetSizePixel());
1106 aBlack
.Erase(0); // opaque
1107 mpAlphaVDev
->DrawTransformBitmapExDirect(aFullTransform
, BitmapEx(aBlack
, aAlphaBitmap
));
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
1130 // now get discrete target pixels; start with OutDev pixel size and evtl.
1131 // intersect with active clipping area
1132 basegfx::B2DRange
aOutPixel(
1135 GetOutputSizePixel().Width(),
1136 GetOutputSizePixel().Height());
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
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
1168 // aVisiblePixelRange contains the reduced output area in
1169 // discrete coordinates. To make it useful everywhere, make it relative to
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);
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
1199 #include <tools/time.hxx>
1200 struct LocalTimeTest
1202 const sal_uInt64 nStartTime
;
1203 LocalTimeTest() : nStartTime(tools::Time::GetSystemTicks()) {}
1206 const sal_uInt64
nEndTime(tools::Time::GetSystemTicks());
1207 const sal_uInt64
nDiffTime(nEndTime
- nStartTime
);
1211 OStringBuffer
aOutput("Time: ");
1212 OString
aNumber(OString::number(nDiffTime
));
1213 aOutput
.append(aNumber
);
1214 OSL_FAIL(aOutput
.getStr());
1220 void OutputDevice::DrawTransformedBitmapEx(
1221 const basegfx::B2DHomMatrix
& rTransformation
,
1222 const BitmapEx
& rBitmapEx
)
1224 assert(!is_double_buffered_window());
1226 if( ImplIsRecordLayout() )
1229 if(rBitmapEx
.IsEmpty())
1232 // MM02 compared to other public methods of OutputDevice
1233 // this test was missing and led to zero-ptr-accesses
1234 if ( !mpGraphics
&& !AcquireGraphics() )
1237 if ( mbInitClipRegion
)
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
)
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()
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
)
1293 aDestPt
.Move(-aOrigin
.getX(), -aOrigin
.getY());
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
);
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
))
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
);
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
);
1352 if ( !TransformAndReduceBitmapExToTargetRange( aFullTransform
, aVisibleRange
, fMaximumArea
) )
1356 if(aVisibleRange
.isEmpty())
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());
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(
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
));
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
);
1482 rNonConstImage
.Draw(this, rPos
, nStyle
, &rSize
);
1484 rNonConstImage
.Draw(this, rPos
, nStyle
);
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() ) );
1531 void OutputDevice::BlendBitmap(
1532 const SalTwoRect
& rPosAry
,
1533 const Bitmap
& rBmp
)
1535 mpGraphics
->BlendBitmap( rPosAry
, *rBmp
.ImplGetSalBitmap(), this );
1538 Bitmap
OutputDevice::BlendBitmapWithAlpha(
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
;
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
)
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;
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
);
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
)));
1632 mpAlphaVDev
->DrawBitmap( aDstRect
.TopLeft(), aAlphaBitmap
);
1633 mpAlphaVDev
->EnableMapMode( bOldMapMode
);
1638 Bitmap
OutputDevice::BlendBitmap(
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
,
1650 const tools::Long
* pMapX
,
1651 const tools::Long
* pMapY
)
1653 BitmapColor aDstCol
;
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
)
1668 for( nY
= 0, nOutY
= nOffY
; nY
< nDstHeight
; nY
++, nOutY
++ )
1670 tools::Long nMapY
= pMapY
[ nY
];
1673 nMapY
= aBmpRect
.Bottom() - nMapY
;
1675 const tools::Long nModY
= ( nOutY
& 0x0FL
) << 4;
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
];
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
);
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
];
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
];
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
);
1752 for( nY
= 0; nY
< nDstHeight
; nY
++ )
1754 tools::Long nMapY
= pMapY
[ nY
];
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
];
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
);
1787 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */