bump product version to 6.4.0.3
[LibreOffice.git] / vcl / source / outdev / bitmap.cxx
blob5db64d06dc4055d24b20485255494961d3cd61e2
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>
22 #include <vcl/bitmap.hxx>
23 #include <vcl/bitmapex.hxx>
24 #include <vcl/bitmapaccess.hxx>
25 #include <vcl/canvastools.hxx>
26 #include <vcl/gdimtf.hxx>
27 #include <vcl/metaact.hxx>
28 #include <config_features.h>
29 #if HAVE_FEATURE_OPENGL
30 #include <vcl/opengl/OpenGLHelper.hxx>
31 #endif
32 #include <vcl/outdev.hxx>
33 #include <vcl/virdev.hxx>
34 #include <vcl/image.hxx>
35 #include <vcl/BitmapMonochromeFilter.hxx>
37 #include <bmpfast.hxx>
38 #include <salgdi.hxx>
39 #include <salbmp.hxx>
41 #include <basegfx/matrix/b2dhommatrixtools.hxx>
42 #include <memory>
43 #include <comphelper/lok.hxx>
44 #include <bitmapwriteaccess.hxx>
45 #include <sal/log.hxx>
46 #include <osl/diagnose.h>
47 #include <tools/helpers.hxx>
48 #include <tools/debug.hxx>
50 void OutputDevice::DrawBitmap( const Point& rDestPt, const Bitmap& rBitmap )
52 assert(!is_double_buffered_window());
54 const Size aSizePix( rBitmap.GetSizePixel() );
55 DrawBitmap( rDestPt, PixelToLogic( aSizePix ), Point(), aSizePix, rBitmap, MetaActionType::BMP );
58 void OutputDevice::DrawBitmap( const Point& rDestPt, const Size& rDestSize, const Bitmap& rBitmap )
60 assert(!is_double_buffered_window());
62 DrawBitmap( rDestPt, rDestSize, Point(), rBitmap.GetSizePixel(), rBitmap, MetaActionType::BMPSCALE );
66 void OutputDevice::DrawBitmap( const Point& rDestPt, const Size& rDestSize,
67 const Point& rSrcPtPixel, const Size& rSrcSizePixel,
68 const Bitmap& rBitmap, const MetaActionType nAction )
70 assert(!is_double_buffered_window());
72 if( ImplIsRecordLayout() )
73 return;
75 if ( RasterOp::Invert == meRasterOp )
77 DrawRect( tools::Rectangle( rDestPt, rDestSize ) );
78 return;
81 Bitmap aBmp( rBitmap );
83 if ( mnDrawMode & ( DrawModeFlags::BlackBitmap | DrawModeFlags::WhiteBitmap |
84 DrawModeFlags::GrayBitmap ) )
86 if ( mnDrawMode & ( DrawModeFlags::BlackBitmap | DrawModeFlags::WhiteBitmap ) )
88 sal_uInt8 cCmpVal;
90 if ( mnDrawMode & DrawModeFlags::BlackBitmap )
91 cCmpVal = 0;
92 else
93 cCmpVal = 255;
95 Color aCol( cCmpVal, cCmpVal, cCmpVal );
96 Push( PushFlags::LINECOLOR | PushFlags::FILLCOLOR );
97 SetLineColor( aCol );
98 SetFillColor( aCol );
99 DrawRect( tools::Rectangle( rDestPt, rDestSize ) );
100 Pop();
101 return;
103 else if( !!aBmp )
105 if ( mnDrawMode & DrawModeFlags::GrayBitmap )
106 aBmp.Convert( BmpConversion::N8BitGreys );
110 if ( mpMetaFile )
112 switch( nAction )
114 case MetaActionType::BMP:
115 mpMetaFile->AddAction( new MetaBmpAction( rDestPt, aBmp ) );
116 break;
118 case MetaActionType::BMPSCALE:
119 mpMetaFile->AddAction( new MetaBmpScaleAction( rDestPt, rDestSize, aBmp ) );
120 break;
122 case MetaActionType::BMPSCALEPART:
123 mpMetaFile->AddAction( new MetaBmpScalePartAction(
124 rDestPt, rDestSize, rSrcPtPixel, rSrcSizePixel, aBmp ) );
125 break;
127 default: break;
131 if ( !IsDeviceOutputNecessary() )
132 return;
134 if ( !mpGraphics && !AcquireGraphics() )
135 return;
137 if ( mbInitClipRegion )
138 InitClipRegion();
140 if ( mbOutputClipped )
141 return;
143 if( !aBmp.IsEmpty() )
145 SalTwoRect aPosAry(rSrcPtPixel.X(), rSrcPtPixel.Y(), rSrcSizePixel.Width(), rSrcSizePixel.Height(),
146 ImplLogicXToDevicePixel(rDestPt.X()), ImplLogicYToDevicePixel(rDestPt.Y()),
147 ImplLogicWidthToDevicePixel(rDestSize.Width()),
148 ImplLogicHeightToDevicePixel(rDestSize.Height()));
150 if ( aPosAry.mnSrcWidth && aPosAry.mnSrcHeight && aPosAry.mnDestWidth && aPosAry.mnDestHeight )
152 const BmpMirrorFlags nMirrFlags = AdjustTwoRect( aPosAry, aBmp.GetSizePixel() );
154 if ( nMirrFlags != BmpMirrorFlags::NONE )
155 aBmp.Mirror( nMirrFlags );
157 if ( aPosAry.mnSrcWidth && aPosAry.mnSrcHeight && aPosAry.mnDestWidth && aPosAry.mnDestHeight )
159 if ( nAction == MetaActionType::BMPSCALE )
160 ScaleBitmap (aBmp, aPosAry);
162 mpGraphics->DrawBitmap( aPosAry, *aBmp.ImplGetSalBitmap(), this );
167 if( mpAlphaVDev )
169 // #i32109#: Make bitmap area opaque
170 mpAlphaVDev->ImplFillOpaqueRectangle( tools::Rectangle(rDestPt, rDestSize) );
174 Bitmap OutputDevice::GetDownsampledBitmap( const Size& rDstSz,
175 const Point& rSrcPt, const Size& rSrcSz,
176 const Bitmap& rBmp, long nMaxBmpDPIX, long nMaxBmpDPIY )
178 Bitmap aBmp( rBmp );
180 if( !aBmp.IsEmpty() )
182 const tools::Rectangle aBmpRect( Point(), aBmp.GetSizePixel() );
183 tools::Rectangle aSrcRect( rSrcPt, rSrcSz );
185 // do cropping if necessary
186 if( aSrcRect.Intersection( aBmpRect ) != aBmpRect )
188 if( !aSrcRect.IsEmpty() )
189 aBmp.Crop( aSrcRect );
190 else
191 aBmp.SetEmpty();
194 if( !aBmp.IsEmpty() )
196 // do downsampling if necessary
197 Size aDstSizeTwip( PixelToLogic(LogicToPixel(rDstSz), MapMode(MapUnit::MapTwip)) );
199 // #103209# Normalize size (mirroring has to happen outside of this method)
200 aDstSizeTwip = Size( labs(aDstSizeTwip.Width()), labs(aDstSizeTwip.Height()) );
202 const Size aBmpSize( aBmp.GetSizePixel() );
203 const double fBmpPixelX = aBmpSize.Width();
204 const double fBmpPixelY = aBmpSize.Height();
205 const double fMaxPixelX = aDstSizeTwip.Width() * nMaxBmpDPIX / 1440.0;
206 const double fMaxPixelY = aDstSizeTwip.Height() * nMaxBmpDPIY / 1440.0;
208 // check, if the bitmap DPI exceeds the maximum DPI (allow 4 pixel rounding tolerance)
209 if( ( ( fBmpPixelX > ( fMaxPixelX + 4 ) ) ||
210 ( fBmpPixelY > ( fMaxPixelY + 4 ) ) ) &&
211 ( fBmpPixelY > 0.0 ) && ( fMaxPixelY > 0.0 ) )
213 // do scaling
214 Size aNewBmpSize;
215 const double fBmpWH = fBmpPixelX / fBmpPixelY;
216 const double fMaxWH = fMaxPixelX / fMaxPixelY;
218 if( fBmpWH < fMaxWH )
220 aNewBmpSize.setWidth( FRound( fMaxPixelY * fBmpWH ) );
221 aNewBmpSize.setHeight( FRound( fMaxPixelY ) );
223 else if( fBmpWH > 0.0 )
225 aNewBmpSize.setWidth( FRound( fMaxPixelX ) );
226 aNewBmpSize.setHeight( FRound( fMaxPixelX / fBmpWH) );
229 if( aNewBmpSize.Width() && aNewBmpSize.Height() )
230 aBmp.Scale( aNewBmpSize );
231 else
232 aBmp.SetEmpty();
237 return aBmp;
240 void OutputDevice::DrawBitmapEx( const Point& rDestPt,
241 const BitmapEx& rBitmapEx )
243 assert(!is_double_buffered_window());
245 if( ImplIsRecordLayout() )
246 return;
248 if( TransparentType::NONE == rBitmapEx.GetTransparentType() )
250 DrawBitmap( rDestPt, rBitmapEx.GetBitmap() );
252 else
254 const Size aSizePix( rBitmapEx.GetSizePixel() );
255 DrawBitmapEx( rDestPt, PixelToLogic( aSizePix ), Point(), aSizePix, rBitmapEx, MetaActionType::BMPEX );
259 void OutputDevice::DrawBitmapEx( const Point& rDestPt, const Size& rDestSize,
260 const BitmapEx& rBitmapEx )
262 assert(!is_double_buffered_window());
264 if( ImplIsRecordLayout() )
265 return;
267 if ( TransparentType::NONE == rBitmapEx.GetTransparentType() )
269 DrawBitmap( rDestPt, rDestSize, rBitmapEx.GetBitmap() );
271 else
273 DrawBitmapEx( rDestPt, rDestSize, Point(), rBitmapEx.GetSizePixel(), rBitmapEx, MetaActionType::BMPEXSCALE );
278 void OutputDevice::DrawBitmapEx( const Point& rDestPt, const Size& rDestSize,
279 const Point& rSrcPtPixel, const Size& rSrcSizePixel,
280 const BitmapEx& rBitmapEx, const MetaActionType nAction )
282 assert(!is_double_buffered_window());
284 if( ImplIsRecordLayout() )
285 return;
287 if( TransparentType::NONE == rBitmapEx.GetTransparentType() )
289 DrawBitmap( rDestPt, rDestSize, rSrcPtPixel, rSrcSizePixel, rBitmapEx.GetBitmap() );
291 else
293 if ( RasterOp::Invert == meRasterOp )
295 DrawRect( tools::Rectangle( rDestPt, rDestSize ) );
296 return;
299 BitmapEx aBmpEx( rBitmapEx );
301 if ( mnDrawMode & ( DrawModeFlags::BlackBitmap | DrawModeFlags::WhiteBitmap |
302 DrawModeFlags::GrayBitmap ) )
304 if ( mnDrawMode & ( DrawModeFlags::BlackBitmap | DrawModeFlags::WhiteBitmap ) )
306 Bitmap aColorBmp( aBmpEx.GetSizePixel(), 1 );
307 sal_uInt8 cCmpVal;
309 if ( mnDrawMode & DrawModeFlags::BlackBitmap )
310 cCmpVal = 0;
311 else
312 cCmpVal = 255;
314 aColorBmp.Erase( Color( cCmpVal, cCmpVal, cCmpVal ) );
316 if( aBmpEx.IsAlpha() )
318 // Create one-bit mask out of alpha channel, by
319 // thresholding it at alpha=0.5. As
320 // DRAWMODE_BLACK/WHITEBITMAP requires monochrome
321 // output, having alpha-induced grey levels is not
322 // acceptable.
323 BitmapEx aMaskEx(aBmpEx.GetAlpha().GetBitmap());
324 BitmapFilter::Filter(aMaskEx, BitmapMonochromeFilter(129));
325 aBmpEx = BitmapEx(aColorBmp, aMaskEx.GetBitmap());
327 else
329 aBmpEx = BitmapEx( aColorBmp, aBmpEx.GetMask() );
332 else if( !!aBmpEx )
334 if ( mnDrawMode & DrawModeFlags::GrayBitmap )
335 aBmpEx.Convert( BmpConversion::N8BitGreys );
339 if ( mpMetaFile )
341 switch( nAction )
343 case MetaActionType::BMPEX:
344 mpMetaFile->AddAction( new MetaBmpExAction( rDestPt, aBmpEx ) );
345 break;
347 case MetaActionType::BMPEXSCALE:
348 mpMetaFile->AddAction( new MetaBmpExScaleAction( rDestPt, rDestSize, aBmpEx ) );
349 break;
351 case MetaActionType::BMPEXSCALEPART:
352 mpMetaFile->AddAction( new MetaBmpExScalePartAction( rDestPt, rDestSize,
353 rSrcPtPixel, rSrcSizePixel, aBmpEx ) );
354 break;
356 default: break;
360 if ( !IsDeviceOutputNecessary() )
361 return;
363 if ( !mpGraphics && !AcquireGraphics() )
364 return;
366 if ( mbInitClipRegion )
367 InitClipRegion();
369 if ( mbOutputClipped )
370 return;
372 DrawDeviceBitmap( rDestPt, rDestSize, rSrcPtPixel, rSrcSizePixel, aBmpEx );
376 Bitmap OutputDevice::GetBitmap( const Point& rSrcPt, const Size& rSize ) const
378 Bitmap aBmp;
379 long nX = ImplLogicXToDevicePixel( rSrcPt.X() );
380 long nY = ImplLogicYToDevicePixel( rSrcPt.Y() );
381 long nWidth = ImplLogicWidthToDevicePixel( rSize.Width() );
382 long nHeight = ImplLogicHeightToDevicePixel( rSize.Height() );
384 if ( mpGraphics || AcquireGraphics() )
386 if ( nWidth > 0 && nHeight > 0 && nX <= (mnOutWidth + mnOutOffX) && nY <= (mnOutHeight + mnOutOffY))
388 tools::Rectangle aRect( Point( nX, nY ), Size( nWidth, nHeight ) );
389 bool bClipped = false;
391 // X-Coordinate outside of draw area?
392 if ( nX < mnOutOffX )
394 nWidth -= ( mnOutOffX - nX );
395 nX = mnOutOffX;
396 bClipped = true;
399 // Y-Coordinate outside of draw area?
400 if ( nY < mnOutOffY )
402 nHeight -= ( mnOutOffY - nY );
403 nY = mnOutOffY;
404 bClipped = true;
407 // Width outside of draw area?
408 if ( (nWidth + nX) > (mnOutWidth + mnOutOffX) )
410 nWidth = mnOutOffX + mnOutWidth - nX;
411 bClipped = true;
414 // Height outside of draw area?
415 if ( (nHeight + nY) > (mnOutHeight + mnOutOffY) )
417 nHeight = mnOutOffY + mnOutHeight - nY;
418 bClipped = true;
421 if ( bClipped )
423 // If the visible part has been clipped, we have to create a
424 // Bitmap with the correct size in which we copy the clipped
425 // Bitmap to the correct position.
426 ScopedVclPtrInstance< VirtualDevice > aVDev( *this );
428 if ( aVDev->SetOutputSizePixel( aRect.GetSize() ) )
430 if ( aVDev->mpGraphics || aVDev->AcquireGraphics() )
432 if ( (nWidth > 0) && (nHeight > 0) )
434 SalTwoRect aPosAry(nX, nY, nWidth, nHeight,
435 (aRect.Left() < mnOutOffX) ? (mnOutOffX - aRect.Left()) : 0L,
436 (aRect.Top() < mnOutOffY) ? (mnOutOffY - aRect.Top()) : 0L,
437 nWidth, nHeight);
438 aVDev->mpGraphics->CopyBits( aPosAry, mpGraphics, this, this );
440 else
442 OSL_ENSURE(false, "CopyBits with zero or negative width or height");
445 aBmp = aVDev->GetBitmap( Point(), aVDev->GetOutputSizePixel() );
447 else
448 bClipped = false;
450 else
451 bClipped = false;
454 if ( !bClipped )
456 std::shared_ptr<SalBitmap> pSalBmp = mpGraphics->GetBitmap( nX, nY, nWidth, nHeight, this );
458 if( pSalBmp )
460 aBmp.ImplSetSalBitmap(pSalBmp);
466 return aBmp;
469 BitmapEx OutputDevice::GetBitmapEx( const Point& rSrcPt, const Size& rSize ) const
472 // #110958# Extract alpha value from VDev, if any
473 if( mpAlphaVDev )
475 Bitmap aAlphaBitmap( mpAlphaVDev->GetBitmap( rSrcPt, rSize ) );
477 // ensure 8 bit alpha
478 if( aAlphaBitmap.GetBitCount() > 8 )
479 aAlphaBitmap.Convert( BmpConversion::N8BitGreys );
481 return BitmapEx(GetBitmap( rSrcPt, rSize ), AlphaMask( aAlphaBitmap ) );
483 else
484 return BitmapEx(GetBitmap( rSrcPt, rSize ));
487 void OutputDevice::DrawDeviceBitmap( const Point& rDestPt, const Size& rDestSize,
488 const Point& rSrcPtPixel, const Size& rSrcSizePixel,
489 BitmapEx& rBitmapEx )
491 assert(!is_double_buffered_window());
493 if (rBitmapEx.IsAlpha())
495 DrawDeviceAlphaBitmap(rBitmapEx.GetBitmap(), rBitmapEx.GetAlpha(), rDestPt, rDestSize, rSrcPtPixel, rSrcSizePixel);
497 else if (!!rBitmapEx)
499 SalTwoRect aPosAry(rSrcPtPixel.X(), rSrcPtPixel.Y(), rSrcSizePixel.Width(), rSrcSizePixel.Height(),
500 ImplLogicXToDevicePixel(rDestPt.X()), ImplLogicYToDevicePixel(rDestPt.Y()),
501 ImplLogicWidthToDevicePixel(rDestSize.Width()),
502 ImplLogicHeightToDevicePixel(rDestSize.Height()));
504 const BmpMirrorFlags nMirrFlags = AdjustTwoRect(aPosAry, rBitmapEx.GetSizePixel());
506 if (aPosAry.mnSrcWidth && aPosAry.mnSrcHeight && aPosAry.mnDestWidth && aPosAry.mnDestHeight)
509 if (nMirrFlags != BmpMirrorFlags::NONE)
510 rBitmapEx.Mirror(nMirrFlags);
512 const SalBitmap* pSalSrcBmp = rBitmapEx.ImplGetBitmapSalBitmap().get();
513 std::shared_ptr<SalBitmap> xMaskBmp = rBitmapEx.ImplGetMaskSalBitmap();
515 if (xMaskBmp)
517 bool bTryDirectPaint(pSalSrcBmp);
519 if (bTryDirectPaint && mpGraphics->DrawAlphaBitmap(aPosAry, *pSalSrcBmp, *xMaskBmp, this))
521 // tried to paint as alpha directly. If tis worked, we are done (except
522 // alpha, see below)
524 else
526 // #4919452# reduce operation area to bounds of
527 // cliprect. since masked transparency involves
528 // creation of a large vdev and copying the screen
529 // content into that (slooow read from framebuffer),
530 // that should considerably increase performance for
531 // large bitmaps and small clippings.
533 // Note that this optimization is a workaround for a
534 // Writer peculiarity, namely, to decompose background
535 // graphics into myriads of disjunct, tiny
536 // rectangles. That otherwise kills us here, since for
537 // transparent output, SAL always prepares the whole
538 // bitmap, if aPosAry contains the whole bitmap (and
539 // it's _not_ to blame for that).
541 // Note the call to ImplPixelToDevicePixel(), since
542 // aPosAry already contains the mnOutOff-offsets, they
543 // also have to be applied to the region
544 tools::Rectangle aClipRegionBounds( ImplPixelToDevicePixel(maRegion).GetBoundRect() );
546 // TODO: Also respect scaling (that's a bit tricky,
547 // since the source points have to move fractional
548 // amounts (which is not possible, thus has to be
549 // emulated by increases copy area)
550 // const double nScaleX( aPosAry.mnDestWidth / aPosAry.mnSrcWidth );
551 // const double nScaleY( aPosAry.mnDestHeight / aPosAry.mnSrcHeight );
553 // for now, only identity scales allowed
554 if (!aClipRegionBounds.IsEmpty() &&
555 aPosAry.mnDestWidth == aPosAry.mnSrcWidth &&
556 aPosAry.mnDestHeight == aPosAry.mnSrcHeight)
558 // now intersect dest rect with clip region
559 aClipRegionBounds.Intersection(tools::Rectangle(aPosAry.mnDestX,
560 aPosAry.mnDestY,
561 aPosAry.mnDestX + aPosAry.mnDestWidth - 1,
562 aPosAry.mnDestY + aPosAry.mnDestHeight - 1));
564 // Note: I could theoretically optimize away the
565 // DrawBitmap below, if the region is empty
566 // here. Unfortunately, cannot rule out that
567 // somebody relies on the side effects.
568 if (!aClipRegionBounds.IsEmpty())
570 aPosAry.mnSrcX += aClipRegionBounds.Left() - aPosAry.mnDestX;
571 aPosAry.mnSrcY += aClipRegionBounds.Top() - aPosAry.mnDestY;
572 aPosAry.mnSrcWidth = aClipRegionBounds.GetWidth();
573 aPosAry.mnSrcHeight = aClipRegionBounds.GetHeight();
575 aPosAry.mnDestX = aClipRegionBounds.Left();
576 aPosAry.mnDestY = aClipRegionBounds.Top();
577 aPosAry.mnDestWidth = aClipRegionBounds.GetWidth();
578 aPosAry.mnDestHeight = aClipRegionBounds.GetHeight();
582 mpGraphics->DrawBitmap(aPosAry, *pSalSrcBmp, *xMaskBmp, this);
585 // #110958# Paint mask to alpha channel. Luckily, the
586 // black and white representation of the mask maps to
587 // the alpha channel
589 // #i25167# Restrict mask painting to _opaque_ areas
590 // of the mask, otherwise we spoil areas where no
591 // bitmap content was ever visible. Interestingly
592 // enough, this can be achieved by taking the mask as
593 // the transparency mask of itself
594 if (mpAlphaVDev)
595 mpAlphaVDev->DrawBitmapEx(rDestPt,
596 rDestSize,
597 BitmapEx(rBitmapEx.GetMask(),
598 rBitmapEx.GetMask()));
600 else
602 mpGraphics->DrawBitmap(aPosAry, *pSalSrcBmp, this);
604 if (mpAlphaVDev)
606 // #i32109#: Make bitmap area opaque
607 mpAlphaVDev->ImplFillOpaqueRectangle( tools::Rectangle(rDestPt, rDestSize) );
614 void OutputDevice::DrawDeviceAlphaBitmap( const Bitmap& rBmp, const AlphaMask& rAlpha,
615 const Point& rDestPt, const Size& rDestSize,
616 const Point& rSrcPtPixel, const Size& rSrcSizePixel )
618 assert(!is_double_buffered_window());
620 Point aOutPt(LogicToPixel(rDestPt));
621 Size aOutSz(LogicToPixel(rDestSize));
622 tools::Rectangle aDstRect(Point(), GetOutputSizePixel());
624 const bool bHMirr = aOutSz.Width() < 0;
625 const bool bVMirr = aOutSz.Height() < 0;
627 ClipToPaintRegion(aDstRect);
629 if (bHMirr)
631 aOutSz.setWidth( -aOutSz.Width() );
632 aOutPt.AdjustX( -(aOutSz.Width() - 1) );
635 if (bVMirr)
637 aOutSz.setHeight( -aOutSz.Height() );
638 aOutPt.AdjustY( -(aOutSz.Height() - 1) );
641 if (!aDstRect.Intersection(tools::Rectangle(aOutPt, aOutSz)).IsEmpty())
643 static const char* pDisableNative = getenv( "SAL_DISABLE_NATIVE_ALPHA");
644 // #i83087# Naturally, system alpha blending cannot work with
645 // separate alpha VDev
647 // Not clear how the above comment relates to the following declaration and initialisation
648 // of bTryDirectPaint. Does bTryDirectPaint being true mean that we can use "system alpha
649 // blending"? Or that we can't? Or are the two not related at all, and should the above
650 // comment actually be better located below, before the "if (mpAlphaVDev)" test?
652 bool bTryDirectPaint(!pDisableNative && !bHMirr && !bVMirr);
654 if (bTryDirectPaint)
656 Point aRelPt = aOutPt + Point(mnOutOffX, mnOutOffY);
657 SalTwoRect aTR(
658 rSrcPtPixel.X(), rSrcPtPixel.Y(),
659 rSrcSizePixel.Width(), rSrcSizePixel.Height(),
660 aRelPt.X(), aRelPt.Y(),
661 aOutSz.Width(), aOutSz.Height());
663 SalBitmap* pSalSrcBmp = rBmp.ImplGetSalBitmap().get();
664 SalBitmap* pSalAlphaBmp = rAlpha.ImplGetSalBitmap().get();
666 // try to blend the alpha bitmap with the alpha virtual device
667 if (mpAlphaVDev)
669 Bitmap aAlphaBitmap( mpAlphaVDev->GetBitmap( aRelPt, aOutSz ) );
670 if (aAlphaBitmap.ImplGetSalBitmap())
672 SalBitmap* pSalAlphaBmp2 = aAlphaBitmap.ImplGetSalBitmap().get();
673 if (mpGraphics->BlendAlphaBitmap(aTR, *pSalSrcBmp, *pSalAlphaBmp, *pSalAlphaBmp2, this))
675 mpAlphaVDev->BlendBitmap(aTR, rAlpha);
676 return;
680 else
682 if (mpGraphics->DrawAlphaBitmap(aTR, *pSalSrcBmp, *pSalAlphaBmp, this))
683 return;
687 // we need to make sure OpenGL never reaches this slow code path
689 #if HAVE_FEATURE_OPENGL
690 assert(!OpenGLHelper::isVCLOpenGLEnabled());
691 #endif
692 tools::Rectangle aBmpRect(Point(), rBmp.GetSizePixel());
693 if (!aBmpRect.Intersection(tools::Rectangle(rSrcPtPixel, rSrcSizePixel)).IsEmpty())
695 Point auxOutPt(LogicToPixel(rDestPt));
696 Size auxOutSz(LogicToPixel(rDestSize));
698 DrawDeviceAlphaBitmapSlowPath(rBmp, rAlpha, aDstRect, aBmpRect, auxOutSz, auxOutPt);
703 namespace
706 struct LinearScaleContext
708 std::unique_ptr<long[]> mpMapX;
709 std::unique_ptr<long[]> mpMapY;
711 std::unique_ptr<long[]> mpMapXOffset;
712 std::unique_ptr<long[]> mpMapYOffset;
714 LinearScaleContext(tools::Rectangle const & aDstRect, tools::Rectangle const & aBitmapRect,
715 Size const & aOutSize, long nOffX, long nOffY)
717 : mpMapX(new long[aDstRect.GetWidth()])
718 , mpMapY(new long[aDstRect.GetHeight()])
719 , mpMapXOffset(new long[aDstRect.GetWidth()])
720 , mpMapYOffset(new long[aDstRect.GetHeight()])
722 const long nSrcWidth = aBitmapRect.GetWidth();
723 const long nSrcHeight = aBitmapRect.GetHeight();
725 generateSimpleMap(
726 nSrcWidth, aDstRect.GetWidth(), aBitmapRect.Left(),
727 aOutSize.Width(), nOffX, mpMapX.get(), mpMapXOffset.get());
729 generateSimpleMap(
730 nSrcHeight, aDstRect.GetHeight(), aBitmapRect.Top(),
731 aOutSize.Height(), nOffY, mpMapY.get(), mpMapYOffset.get());
734 private:
736 static void generateSimpleMap(long nSrcDimension, long nDstDimension, long nDstLocation,
737 long nOutDimention, long nOffset, long* pMap, long* pMapOffset)
740 const double fReverseScale = (std::abs(nOutDimention) > 1) ? (nSrcDimension - 1) / double(std::abs(nOutDimention) - 1) : 0.0;
742 long nSampleRange = std::max(0L, nSrcDimension - 2);
744 for (long i = 0; i < nDstDimension; i++)
746 double fTemp = std::abs((nOffset + i) * fReverseScale);
748 pMap[i] = MinMax(nDstLocation + long(fTemp), 0, nSampleRange);
749 pMapOffset[i] = static_cast<long>((fTemp - pMap[i]) * 128.0);
753 public:
754 bool blendBitmap(
755 const BitmapWriteAccess* pDestination,
756 const BitmapReadAccess* pSource,
757 const BitmapReadAccess* pSourceAlpha,
758 const long nDstWidth,
759 const long nDstHeight)
761 if (pSource && pSourceAlpha && pDestination)
763 ScanlineFormat nSourceFormat = pSource->GetScanlineFormat();
764 ScanlineFormat nDestinationFormat = pDestination->GetScanlineFormat();
766 switch (nSourceFormat)
768 case ScanlineFormat::N24BitTcRgb:
769 case ScanlineFormat::N24BitTcBgr:
771 if ( (nSourceFormat == ScanlineFormat::N24BitTcBgr && nDestinationFormat == ScanlineFormat::N32BitTcBgra)
772 || (nSourceFormat == ScanlineFormat::N24BitTcRgb && nDestinationFormat == ScanlineFormat::N32BitTcRgba))
774 blendBitmap24(pDestination, pSource, pSourceAlpha, nDstWidth, nDstHeight);
775 return true;
778 break;
779 default: break;
782 return false;
785 void blendBitmap24(
786 const BitmapWriteAccess* pDestination,
787 const BitmapReadAccess* pSource,
788 const BitmapReadAccess* pSourceAlpha,
789 const long nDstWidth,
790 const long nDstHeight)
792 Scanline pLine0, pLine1;
793 Scanline pLineAlpha0, pLineAlpha1;
794 Scanline pColorSample1, pColorSample2;
795 Scanline pDestScanline;
797 long nColor1Line1, nColor2Line1, nColor3Line1;
798 long nColor1Line2, nColor2Line2, nColor3Line2;
799 long nAlphaLine1, nAlphaLine2;
801 sal_uInt8 nColor1, nColor2, nColor3, nAlpha;
803 for (long nY = 0; nY < nDstHeight; nY++)
805 const long nMapY = mpMapY[nY];
806 const long nMapFY = mpMapYOffset[nY];
808 pLine0 = pSource->GetScanline(nMapY);
809 // tdf#95481 guard nMapY + 1 to be within bounds
810 pLine1 = (nMapY + 1 < pSource->Height()) ? pSource->GetScanline(nMapY + 1) : pLine0;
812 pLineAlpha0 = pSourceAlpha->GetScanline(nMapY);
813 // tdf#95481 guard nMapY + 1 to be within bounds
814 pLineAlpha1 = (nMapY + 1 < pSourceAlpha->Height()) ? pSourceAlpha->GetScanline(nMapY + 1) : pLineAlpha0;
816 pDestScanline = pDestination->GetScanline(nY);
818 for (long nX = 0; nX < nDstWidth; nX++)
820 const long nMapX = mpMapX[nX];
821 const long nMapFX = mpMapXOffset[nX];
823 pColorSample1 = pLine0 + 3 * nMapX;
824 pColorSample2 = (nMapX + 1 < pSource->Width()) ? pColorSample1 + 3 : pColorSample1;
825 nColor1Line1 = (static_cast<long>(*pColorSample1) << 7) + nMapFX * (static_cast<long>(*pColorSample2) - *pColorSample1);
827 pColorSample1++;
828 pColorSample2++;
829 nColor2Line1 = (static_cast<long>(*pColorSample1) << 7) + nMapFX * (static_cast<long>(*pColorSample2) - *pColorSample1);
831 pColorSample1++;
832 pColorSample2++;
833 nColor3Line1 = (static_cast<long>(*pColorSample1) << 7) + nMapFX * (static_cast<long>(*pColorSample2) - *pColorSample1);
835 pColorSample1 = pLine1 + 3 * nMapX;
836 pColorSample2 = (nMapX + 1 < pSource->Width()) ? pColorSample1 + 3 : pColorSample1;
837 nColor1Line2 = (static_cast<long>(*pColorSample1) << 7) + nMapFX * (static_cast<long>(*pColorSample2) - *pColorSample1);
839 pColorSample1++;
840 pColorSample2++;
841 nColor2Line2 = (static_cast<long>(*pColorSample1) << 7) + nMapFX * (static_cast<long>(*pColorSample2) - *pColorSample1);
843 pColorSample1++;
844 pColorSample2++;
845 nColor3Line2 = (static_cast<long>(*pColorSample1) << 7) + nMapFX * (static_cast<long>(*pColorSample2) - *pColorSample1);
847 pColorSample1 = pLineAlpha0 + nMapX;
848 pColorSample2 = (nMapX + 1 < pSourceAlpha->Width()) ? pColorSample1 + 1 : pColorSample1;
849 nAlphaLine1 = (static_cast<long>(*pColorSample1) << 7) + nMapFX * (static_cast<long>(*pColorSample2) - *pColorSample1);
851 pColorSample1 = pLineAlpha1 + nMapX;
852 pColorSample2 = (nMapX + 1 < pSourceAlpha->Width()) ? pColorSample1 + 1 : pColorSample1;
853 nAlphaLine2 = (static_cast<long>(*pColorSample1) << 7) + nMapFX * (static_cast<long>(*pColorSample2) - *pColorSample1);
855 nColor1 = (nColor1Line1 + nMapFY * ((nColor1Line2 >> 7) - (nColor1Line1 >> 7))) >> 7;
856 nColor2 = (nColor2Line1 + nMapFY * ((nColor2Line2 >> 7) - (nColor2Line1 >> 7))) >> 7;
857 nColor3 = (nColor3Line1 + nMapFY * ((nColor3Line2 >> 7) - (nColor3Line1 >> 7))) >> 7;
859 nAlpha = (nAlphaLine1 + nMapFY * ((nAlphaLine2 >> 7) - (nAlphaLine1 >> 7))) >> 7;
861 *pDestScanline = ColorChannelMerge(*pDestScanline, nColor1, nAlpha);
862 pDestScanline++;
863 *pDestScanline = ColorChannelMerge(*pDestScanline, nColor2, nAlpha);
864 pDestScanline++;
865 *pDestScanline = ColorChannelMerge(*pDestScanline, nColor3, nAlpha);
866 pDestScanline++;
867 pDestScanline++;
873 struct TradScaleContext
875 std::unique_ptr<long[]> mpMapX;
876 std::unique_ptr<long[]> mpMapY;
878 TradScaleContext(tools::Rectangle const & aDstRect, tools::Rectangle const & aBitmapRect,
879 Size const & aOutSize, long nOffX, long nOffY)
881 : mpMapX(new long[aDstRect.GetWidth()])
882 , mpMapY(new long[aDstRect.GetHeight()])
884 const long nSrcWidth = aBitmapRect.GetWidth();
885 const long nSrcHeight = aBitmapRect.GetHeight();
887 const bool bHMirr = aOutSize.Width() < 0;
888 const bool bVMirr = aOutSize.Height() < 0;
890 generateSimpleMap(
891 nSrcWidth, aDstRect.GetWidth(), aBitmapRect.Left(),
892 aOutSize.Width(), nOffX, bHMirr, mpMapX.get());
894 generateSimpleMap(
895 nSrcHeight, aDstRect.GetHeight(), aBitmapRect.Top(),
896 aOutSize.Height(), nOffY, bVMirr, mpMapY.get());
899 private:
901 static void generateSimpleMap(long nSrcDimension, long nDstDimension, long nDstLocation,
902 long nOutDimention, long nOffset, bool bMirror, long* pMap)
904 long nMirrorOffset = 0;
906 if (bMirror)
907 nMirrorOffset = (nDstLocation << 1) + nSrcDimension - 1;
909 for (long i = 0; i < nDstDimension; ++i, ++nOffset)
911 pMap[i] = nDstLocation + nOffset * nSrcDimension / nOutDimention;
912 if (bMirror)
913 pMap[i] = nMirrorOffset - pMap[i];
919 } // end anonymous namespace
921 void OutputDevice::DrawDeviceAlphaBitmapSlowPath(const Bitmap& rBitmap,
922 const AlphaMask& rAlpha, tools::Rectangle aDstRect, tools::Rectangle aBmpRect, Size const & aOutSize, Point const & aOutPoint)
924 assert(!is_double_buffered_window());
926 VirtualDevice* pOldVDev = mpAlphaVDev;
928 const bool bHMirr = aOutSize.Width() < 0;
929 const bool bVMirr = aOutSize.Height() < 0;
931 // The scaling in this code path produces really ugly results - it
932 // does the most trivial scaling with no smoothing.
933 GDIMetaFile* pOldMetaFile = mpMetaFile;
934 const bool bOldMap = mbMap;
936 mpMetaFile = nullptr; // fdo#55044 reset before GetBitmap!
937 mbMap = false;
939 Bitmap aBmp(GetBitmap(aDstRect.TopLeft(), aDstRect.GetSize()));
941 // #109044# The generated bitmap need not necessarily be
942 // of aDstRect dimensions, it's internally clipped to
943 // window bounds. Thus, we correct the dest size here,
944 // since we later use it (in nDstWidth/Height) for pixel
945 // access)
946 // #i38887# reading from screen may sometimes fail
947 if (aBmp.ImplGetSalBitmap())
949 aDstRect.SetSize(aBmp.GetSizePixel());
952 const long nDstWidth = aDstRect.GetWidth();
953 const long nDstHeight = aDstRect.GetHeight();
955 // calculate offset in original bitmap
956 // in RTL case this is a little more complicated since the contents of the
957 // bitmap is not mirrored (it never is), however the paint region and bmp region
958 // are in mirrored coordinates, so the intersection of (aOutPt,aOutSz) with these
959 // is content wise somewhere else and needs to take mirroring into account
960 const long nOffX = IsRTLEnabled()
961 ? aOutSize.Width() - aDstRect.GetWidth() - (aDstRect.Left() - aOutPoint.X())
962 : aDstRect.Left() - aOutPoint.X();
964 const long nOffY = aDstRect.Top() - aOutPoint.Y();
966 TradScaleContext aTradContext(aDstRect, aBmpRect, aOutSize, nOffX, nOffY);
968 Bitmap::ScopedReadAccess pBitmapReadAccess(const_cast<Bitmap&>(rBitmap));
969 AlphaMask::ScopedReadAccess pAlphaReadAccess(const_cast<AlphaMask&>(rAlpha));
971 DBG_ASSERT( pAlphaReadAccess->GetScanlineFormat() == ScanlineFormat::N8BitPal ||
972 pAlphaReadAccess->GetScanlineFormat() == ScanlineFormat::N8BitTcMask,
973 "OutputDevice::ImplDrawAlpha(): non-8bit alpha no longer supported!" );
975 // #i38887# reading from screen may sometimes fail
976 if (aBmp.ImplGetSalBitmap())
978 Bitmap aNewBitmap;
980 if (mpAlphaVDev)
982 aNewBitmap = BlendBitmapWithAlpha(
983 aBmp, pBitmapReadAccess.get(), pAlphaReadAccess.get(),
984 aDstRect,
985 nOffY, nDstHeight,
986 nOffX, nDstWidth,
987 aTradContext.mpMapX.get(), aTradContext.mpMapY.get() );
989 else
991 LinearScaleContext aLinearContext(aDstRect, aBmpRect, aOutSize, nOffX, nOffY);
993 if (aLinearContext.blendBitmap( BitmapScopedWriteAccess(aBmp).get(), pBitmapReadAccess.get(), pAlphaReadAccess.get(),
994 nDstWidth, nDstHeight))
996 aNewBitmap = aBmp;
998 else
1000 aNewBitmap = BlendBitmap(
1001 aBmp, pBitmapReadAccess.get(), pAlphaReadAccess.get(),
1002 nOffY, nDstHeight,
1003 nOffX, nDstWidth,
1004 aBmpRect, aOutSize,
1005 bHMirr, bVMirr,
1006 aTradContext.mpMapX.get(), aTradContext.mpMapY.get() );
1010 // #110958# Disable alpha VDev, we're doing the necessary
1011 // stuff explicitly further below
1012 if (mpAlphaVDev)
1013 mpAlphaVDev = nullptr;
1015 DrawBitmap(aDstRect.TopLeft(), aNewBitmap);
1017 // #110958# Enable alpha VDev again
1018 mpAlphaVDev = pOldVDev;
1021 mbMap = bOldMap;
1022 mpMetaFile = pOldMetaFile;
1025 void OutputDevice::ScaleBitmap (Bitmap &rBmp, SalTwoRect &rPosAry)
1027 const double nScaleX = rPosAry.mnDestWidth / static_cast<double>( rPosAry.mnSrcWidth );
1028 const double nScaleY = rPosAry.mnDestHeight / static_cast<double>( rPosAry.mnSrcHeight );
1030 // If subsampling, use Bitmap::Scale for subsampling for better quality.
1031 if ( nScaleX < 1.0 || nScaleY < 1.0 )
1033 rBmp.Scale ( nScaleX, nScaleY );
1034 rPosAry.mnSrcWidth = rPosAry.mnDestWidth;
1035 rPosAry.mnSrcHeight = rPosAry.mnDestHeight;
1039 bool OutputDevice::DrawTransformBitmapExDirect(
1040 const basegfx::B2DHomMatrix& aFullTransform,
1041 const BitmapEx& rBitmapEx)
1043 assert(!is_double_buffered_window());
1045 bool bDone = false;
1047 // try to paint directly
1048 const basegfx::B2DPoint aNull(aFullTransform * basegfx::B2DPoint(0.0, 0.0));
1049 const basegfx::B2DPoint aTopX(aFullTransform * basegfx::B2DPoint(1.0, 0.0));
1050 const basegfx::B2DPoint aTopY(aFullTransform * basegfx::B2DPoint(0.0, 1.0));
1051 SalBitmap* pSalSrcBmp = rBitmapEx.GetBitmap().ImplGetSalBitmap().get();
1052 SalBitmap* pSalAlphaBmp = nullptr;
1054 if(rBitmapEx.IsTransparent())
1056 if(rBitmapEx.IsAlpha())
1058 pSalAlphaBmp = rBitmapEx.GetAlpha().ImplGetSalBitmap().get();
1060 else
1062 pSalAlphaBmp = rBitmapEx.GetMask().ImplGetSalBitmap().get();
1066 bDone = mpGraphics->DrawTransformedBitmap(
1067 aNull,
1068 aTopX,
1069 aTopY,
1070 *pSalSrcBmp,
1071 pSalAlphaBmp,
1072 this);
1074 return bDone;
1077 bool OutputDevice::TransformAndReduceBitmapExToTargetRange(
1078 const basegfx::B2DHomMatrix& aFullTransform,
1079 basegfx::B2DRange &aVisibleRange,
1080 double &fMaximumArea)
1082 // limit TargetRange to existing pixels (if pixel device)
1083 // first get discrete range of object
1084 basegfx::B2DRange aFullPixelRange(aVisibleRange);
1086 aFullPixelRange.transform(aFullTransform);
1088 if(basegfx::fTools::equalZero(aFullPixelRange.getWidth()) || basegfx::fTools::equalZero(aFullPixelRange.getHeight()))
1090 // object is outside of visible area
1091 return false;
1094 // now get discrete target pixels; start with OutDev pixel size and evtl.
1095 // intersect with active clipping area
1096 basegfx::B2DRange aOutPixel(
1097 0.0,
1098 0.0,
1099 GetOutputSizePixel().Width(),
1100 GetOutputSizePixel().Height());
1102 if(IsClipRegion())
1104 tools::Rectangle aRegionRectangle(GetActiveClipRegion().GetBoundRect());
1106 // caution! Range from rectangle, one too much (!)
1107 aRegionRectangle.AdjustRight(-1);
1108 aRegionRectangle.AdjustBottom(-1);
1109 aOutPixel.intersect( vcl::unotools::b2DRectangleFromRectangle(aRegionRectangle) );
1112 if(aOutPixel.isEmpty())
1114 // no active output area
1115 return false;
1118 // if aFullPixelRange is not completely inside of aOutPixel,
1119 // reduction of target pixels is possible
1120 basegfx::B2DRange aVisiblePixelRange(aFullPixelRange);
1122 if(!aOutPixel.isInside(aFullPixelRange))
1124 aVisiblePixelRange.intersect(aOutPixel);
1126 if(aVisiblePixelRange.isEmpty())
1128 // nothing in visible part, reduces to nothing
1129 return false;
1132 // aVisiblePixelRange contains the reduced output area in
1133 // discrete coordinates. To make it useful everywhere, make it relative to
1134 // the object range
1135 basegfx::B2DHomMatrix aMakeVisibleRangeRelative;
1137 aVisibleRange = aVisiblePixelRange;
1138 aMakeVisibleRangeRelative.translate(
1139 -aFullPixelRange.getMinX(),
1140 -aFullPixelRange.getMinY());
1141 aMakeVisibleRangeRelative.scale(
1142 1.0 / aFullPixelRange.getWidth(),
1143 1.0 / aFullPixelRange.getHeight());
1144 aVisibleRange.transform(aMakeVisibleRangeRelative);
1147 // for pixel devices, do *not* limit size, else OutputDevice::DrawDeviceAlphaBitmap
1148 // will create another, badly scaled bitmap to do the job. Nonetheless, do a
1149 // maximum clipping of something big (1600x1280x2). Add 1.0 to avoid rounding
1150 // errors in rough estimations
1151 const double fNewMaxArea(aVisiblePixelRange.getWidth() * aVisiblePixelRange.getHeight());
1153 fMaximumArea = std::min(4096000.0, fNewMaxArea + 1.0);
1155 return true;
1158 void OutputDevice::DrawTransformedBitmapEx(
1159 const basegfx::B2DHomMatrix& rTransformation,
1160 const BitmapEx& rBitmapEx)
1162 assert(!is_double_buffered_window());
1164 if( ImplIsRecordLayout() )
1165 return;
1167 if(rBitmapEx.IsEmpty())
1168 return;
1170 // decompose matrix to check rotation and shear
1171 basegfx::B2DVector aScale, aTranslate;
1172 double fRotate, fShearX;
1173 rTransformation.decompose(aScale, aTranslate, fRotate, fShearX);
1174 const bool bRotated(!basegfx::fTools::equalZero(fRotate));
1175 const bool bSheared(!basegfx::fTools::equalZero(fShearX));
1176 const bool bMirroredX(basegfx::fTools::less(aScale.getX(), 0.0));
1177 const bool bMirroredY(basegfx::fTools::less(aScale.getY(), 0.0));
1179 const bool bMetafile = mpMetaFile != nullptr;
1181 if(!bRotated && !bSheared && !bMirroredX && !bMirroredY)
1183 // with no rotation, shear or mirroring it can be mapped to DrawBitmapEx
1184 // do *not* execute the mirroring here, it's done in the fallback
1185 // #i124580# the correct DestSize needs to be calculated based on MaxXY values
1186 Point aDestPt(basegfx::fround(aTranslate.getX()), basegfx::fround(aTranslate.getY()));
1187 const Size aDestSize(
1188 basegfx::fround(aScale.getX() + aTranslate.getX()) - aDestPt.X(),
1189 basegfx::fround(aScale.getY() + aTranslate.getY()) - aDestPt.Y());
1190 const Point aOrigin = GetMapMode().GetOrigin();
1191 if (!bMetafile && comphelper::LibreOfficeKit::isActive() && GetMapMode().GetMapUnit() != MapUnit::MapPixel)
1193 aDestPt.Move(aOrigin.getX(), aOrigin.getY());
1194 EnableMapMode(false);
1197 DrawBitmapEx(aDestPt, aDestSize, rBitmapEx);
1198 if (!bMetafile && comphelper::LibreOfficeKit::isActive() && GetMapMode().GetMapUnit() != MapUnit::MapPixel)
1200 EnableMapMode();
1201 aDestPt.Move(-aOrigin.getX(), -aOrigin.getY());
1203 return;
1206 // we have rotation,shear or mirror, check if some crazy mode needs the
1207 // created transformed bitmap
1208 const bool bInvert(RasterOp::Invert == meRasterOp);
1209 const bool bBitmapChangedColor(mnDrawMode & (DrawModeFlags::BlackBitmap | DrawModeFlags::WhiteBitmap | DrawModeFlags::GrayBitmap ));
1210 bool bDone(false);
1211 basegfx::B2DHomMatrix aFullTransform(GetViewTransformation() * rTransformation);
1212 const bool bTryDirectPaint(!bInvert && !bBitmapChangedColor && !bMetafile );
1214 if(bTryDirectPaint)
1216 bDone = DrawTransformBitmapExDirect(aFullTransform, rBitmapEx);
1219 if(!bDone)
1221 // take the fallback when no rotate and shear, but mirror (else we would have done this above)
1222 if(!bRotated && !bSheared)
1224 // with no rotation or shear it can be mapped to DrawBitmapEx
1225 // do *not* execute the mirroring here, it's done in the fallback
1226 // #i124580# the correct DestSize needs to be calculated based on MaxXY values
1227 const Point aDestPt(basegfx::fround(aTranslate.getX()), basegfx::fround(aTranslate.getY()));
1228 const Size aDestSize(
1229 basegfx::fround(aScale.getX() + aTranslate.getX()) - aDestPt.X(),
1230 basegfx::fround(aScale.getY() + aTranslate.getY()) - aDestPt.Y());
1232 DrawBitmapEx(aDestPt, aDestSize, rBitmapEx);
1233 return;
1236 assert(bSheared || bRotated); // at this point we are either sheared or rotated or both
1238 // fallback; create transformed bitmap the hard way (back-transform
1239 // the pixels) and paint
1240 basegfx::B2DRange aVisibleRange(0.0, 0.0, 1.0, 1.0);
1242 // limit maximum area to something looking good for non-pixel-based targets (metafile, printer)
1243 // by using a fixed minimum (allow at least, but no need to utilize) for good smoothing and an area
1244 // dependent of original size for good quality when e.g. rotated/sheared. Still, limit to a maximum
1245 // to avoid crashes/resource problems (ca. 1500x3000 here)
1246 const Size& rOriginalSizePixel(rBitmapEx.GetSizePixel());
1247 const double fOrigArea(rOriginalSizePixel.Width() * rOriginalSizePixel.Height() * 0.5);
1248 const double fOrigAreaScaled(fOrigArea * 1.44);
1249 double fMaximumArea(std::min(4500000.0, std::max(1000000.0, fOrigAreaScaled)));
1251 if(!bMetafile)
1253 if ( !TransformAndReduceBitmapExToTargetRange( aFullTransform, aVisibleRange, fMaximumArea ) )
1254 return;
1257 if(!aVisibleRange.isEmpty())
1259 BitmapEx aTransformed(rBitmapEx);
1261 // #122923# when the result needs an alpha channel due to being rotated or sheared
1262 // and thus uncovering areas, add these channels so that the own transformer (used
1263 // in getTransformed) also creates a transformed alpha channel
1264 if(!aTransformed.IsTransparent() && (bSheared || bRotated))
1266 // parts will be uncovered, extend aTransformed with a mask bitmap
1267 const Bitmap aContent(aTransformed.GetBitmap());
1269 AlphaMask aMaskBmp(aContent.GetSizePixel());
1270 aMaskBmp.Erase(0);
1272 aTransformed = BitmapEx(aContent, aMaskBmp);
1275 // Remove scaling from aFulltransform: we transform due to shearing or rotation, scaling
1276 // will happen according to aDestSize.
1277 basegfx::B2DVector aFullScale, aFullTranslate;
1278 double fFullRotate, fFullShearX;
1279 aFullTransform.decompose(aFullScale, aFullTranslate, fFullRotate, fFullShearX);
1280 // Require positive scaling, negative scaling would loose horizontal or vertical flip.
1281 if (aFullScale.getX() > 0 && aFullScale.getY() > 0)
1283 basegfx::B2DHomMatrix aTransform = basegfx::utils::createScaleB2DHomMatrix(
1284 rOriginalSizePixel.getWidth() / aFullScale.getX(),
1285 rOriginalSizePixel.getHeight() / aFullScale.getY());
1286 aFullTransform *= aTransform;
1289 double fSourceRatio = 1.0;
1290 if (rOriginalSizePixel.getHeight() != 0)
1292 fSourceRatio = rOriginalSizePixel.getWidth() / rOriginalSizePixel.getHeight();
1294 double fTargetRatio = 1.0;
1295 if (aFullScale.getY() != 0)
1297 fTargetRatio = aFullScale.getX() / aFullScale.getY();
1299 bool bAspectRatioKept = rtl::math::approxEqual(fSourceRatio, fTargetRatio);
1300 if (bSheared || !bAspectRatioKept)
1302 // Not only rotation, or scaling does not keep aspect ratio.
1303 aTransformed = aTransformed.getTransformed(
1304 aFullTransform,
1305 aVisibleRange,
1306 fMaximumArea);
1308 else
1310 // Just rotation, can do that directly.
1311 fFullRotate = fmod(fFullRotate * -1, F_2PI);
1312 if (fFullRotate < 0)
1314 fFullRotate += F_2PI;
1316 long nAngle10 = basegfx::fround(basegfx::rad2deg(fFullRotate) * 10);
1317 aTransformed.Rotate(nAngle10, COL_TRANSPARENT);
1319 basegfx::B2DRange aTargetRange(0.0, 0.0, 1.0, 1.0);
1321 // get logic object target range
1322 aTargetRange.transform(rTransformation);
1324 // get from unified/relative VisibleRange to logoc one
1325 aVisibleRange.transform(
1326 basegfx::utils::createScaleTranslateB2DHomMatrix(
1327 aTargetRange.getRange(),
1328 aTargetRange.getMinimum()));
1330 // extract point and size; do not remove size, the bitmap may have been prepared reduced by purpose
1331 // #i124580# the correct DestSize needs to be calculated based on MaxXY values
1332 const Point aDestPt(basegfx::fround(aVisibleRange.getMinX()), basegfx::fround(aVisibleRange.getMinY()));
1333 const Size aDestSize(
1334 basegfx::fround(aVisibleRange.getMaxX()) - aDestPt.X(),
1335 basegfx::fround(aVisibleRange.getMaxY()) - aDestPt.Y());
1337 DrawBitmapEx(aDestPt, aDestSize, aTransformed);
1342 void OutputDevice::DrawShadowBitmapEx(
1343 const BitmapEx& rBitmapEx,
1344 ::Color aShadowColor)
1346 Bitmap::ScopedReadAccess pReadAccess(const_cast<Bitmap&>(rBitmapEx.maBitmap));
1348 if(!pReadAccess)
1349 return;
1351 for(long y(0); y < pReadAccess->Height(); y++)
1353 for(long x(0); x < pReadAccess->Width(); x++)
1355 const BitmapColor aColor = pReadAccess->GetColor(y, x);
1356 sal_uInt16 nLuminance(static_cast<sal_uInt16>(aColor.GetLuminance()) + 1);
1357 const Color aDestColor(
1358 static_cast<sal_uInt8>((nLuminance * static_cast<sal_uInt16>(aShadowColor.GetRed())) >> 8),
1359 static_cast<sal_uInt8>((nLuminance * static_cast<sal_uInt16>(aShadowColor.GetGreen())) >> 8),
1360 static_cast<sal_uInt8>((nLuminance * static_cast<sal_uInt16>(aShadowColor.GetBlue())) >> 8));
1361 DrawPixel(Point(x,y), aDestColor);
1366 void OutputDevice::DrawImage( const Point& rPos, const Image& rImage, DrawImageFlags nStyle )
1368 assert(!is_double_buffered_window());
1370 DrawImage( rPos, Size(), rImage, nStyle );
1373 void OutputDevice::DrawImage( const Point& rPos, const Size& rSize,
1374 const Image& rImage, DrawImageFlags nStyle )
1376 assert(!is_double_buffered_window());
1378 bool bIsSizeValid = rSize.getWidth() != 0 && rSize.getHeight() != 0;
1380 if (!ImplIsRecordLayout())
1382 Image& rNonConstImage = const_cast<Image&>(rImage);
1383 if (bIsSizeValid)
1384 rNonConstImage.Draw(this, rPos, nStyle, &rSize);
1385 else
1386 rNonConstImage.Draw(this, rPos, nStyle);
1390 namespace
1392 // Co = Cs + Cd*(1-As) premultiplied alpha -or-
1393 // Co = (AsCs + AdCd*(1-As)) / Ao
1394 sal_uInt8 CalcColor( const sal_uInt8 nSourceColor, const sal_uInt8 nSourceAlpha,
1395 const sal_uInt8 nDstAlpha, const sal_uInt8 nResAlpha, const sal_uInt8 nDestColor )
1397 int c = nResAlpha ? ( static_cast<int>(nSourceAlpha)*nSourceColor + static_cast<int>(nDstAlpha)*nDestColor -
1398 static_cast<int>(nDstAlpha)*nDestColor*nSourceAlpha/255 ) / static_cast<int>(nResAlpha) : 0;
1399 return sal_uInt8( c );
1402 BitmapColor AlphaBlend( int nX, int nY,
1403 const long nMapX,
1404 const long nMapY,
1405 BitmapReadAccess const * pP,
1406 BitmapReadAccess const * pA,
1407 BitmapReadAccess const * pB,
1408 BitmapWriteAccess const * pAlphaW,
1409 sal_uInt8& nResAlpha )
1411 BitmapColor aDstCol,aSrcCol;
1412 aSrcCol = pP->GetColor( nMapY, nMapX );
1413 aDstCol = pB->GetColor( nY, nX );
1415 // vcl stores transparency, not alpha - invert it
1416 const sal_uInt8 nSrcAlpha = 255 - pA->GetPixelIndex( nMapY, nMapX );
1417 const sal_uInt8 nDstAlpha = 255 - pAlphaW->GetPixelIndex( nY, nX );
1419 // Perform porter-duff compositing 'over' operation
1421 // Co = Cs + Cd*(1-As)
1422 // Ad = As + Ad*(1-As)
1423 nResAlpha = static_cast<int>(nSrcAlpha) + static_cast<int>(nDstAlpha) - static_cast<int>(nDstAlpha)*nSrcAlpha/255;
1425 aDstCol.SetRed( CalcColor( aSrcCol.GetRed(), nSrcAlpha, nDstAlpha, nResAlpha, aDstCol.GetRed() ) );
1426 aDstCol.SetBlue( CalcColor( aSrcCol.GetBlue(), nSrcAlpha, nDstAlpha, nResAlpha, aDstCol.GetBlue() ) );
1427 aDstCol.SetGreen( CalcColor( aSrcCol.GetGreen(), nSrcAlpha, nDstAlpha, nResAlpha, aDstCol.GetGreen() ) );
1429 return aDstCol;
1433 void OutputDevice::BlendBitmap(
1434 const SalTwoRect& rPosAry,
1435 const Bitmap& rBmp )
1437 mpGraphics->BlendBitmap( rPosAry, *rBmp.ImplGetSalBitmap(), this );
1440 Bitmap OutputDevice::BlendBitmapWithAlpha(
1441 Bitmap& aBmp,
1442 BitmapReadAccess const * pP,
1443 BitmapReadAccess const * pA,
1444 const tools::Rectangle& aDstRect,
1445 const sal_Int32 nOffY,
1446 const sal_Int32 nDstHeight,
1447 const sal_Int32 nOffX,
1448 const sal_Int32 nDstWidth,
1449 const long* pMapX,
1450 const long* pMapY )
1453 BitmapColor aDstCol;
1454 Bitmap res;
1455 int nX, nY;
1456 sal_uInt8 nResAlpha;
1458 SAL_WARN_IF( !mpAlphaVDev, "vcl.gdi", "BlendBitmapWithAlpha(): call me only with valid alpha VirtualDevice!" );
1460 bool bOldMapMode( mpAlphaVDev->IsMapModeEnabled() );
1461 mpAlphaVDev->EnableMapMode(false);
1463 Bitmap aAlphaBitmap( mpAlphaVDev->GetBitmap( aDstRect.TopLeft(), aDstRect.GetSize() ) );
1464 BitmapScopedWriteAccess pAlphaW(aAlphaBitmap);
1466 if( GetBitCount() <= 8 )
1468 Bitmap aDither( aBmp.GetSizePixel(), 8 );
1469 BitmapColor aIndex( 0 );
1470 Bitmap::ScopedReadAccess pB(aBmp);
1471 BitmapScopedWriteAccess pW(aDither);
1473 if (pB && pP && pA && pW && pAlphaW)
1475 int nOutY;
1477 for( nY = 0, nOutY = nOffY; nY < nDstHeight; nY++, nOutY++ )
1479 const long nMapY = pMapY[ nY ];
1480 const long nModY = ( nOutY & 0x0FL ) << 4;
1481 int nOutX;
1483 Scanline pScanline = pW->GetScanline(nY);
1484 Scanline pScanlineAlpha = pAlphaW->GetScanline(nY);
1485 for( nX = 0, nOutX = nOffX; nX < nDstWidth; nX++, nOutX++ )
1487 const long nMapX = pMapX[ nX ];
1488 const sal_uLong nD = nVCLDitherLut[ nModY | ( nOutX & 0x0FL ) ];
1490 aDstCol = AlphaBlend( nX, nY, nMapX, nMapY, pP, pA, pB.get(), pAlphaW.get(), nResAlpha );
1492 aIndex.SetIndex( static_cast<sal_uInt8>( nVCLRLut[ ( nVCLLut[ aDstCol.GetRed() ] + nD ) >> 16 ] +
1493 nVCLGLut[ ( nVCLLut[ aDstCol.GetGreen() ] + nD ) >> 16 ] +
1494 nVCLBLut[ ( nVCLLut[ aDstCol.GetBlue() ] + nD ) >> 16 ] ) );
1495 pW->SetPixelOnData( pScanline, nX, aIndex );
1497 aIndex.SetIndex( static_cast<sal_uInt8>( nVCLRLut[ ( nVCLLut[ 255-nResAlpha ] + nD ) >> 16 ] +
1498 nVCLGLut[ ( nVCLLut[ 255-nResAlpha ] + nD ) >> 16 ] +
1499 nVCLBLut[ ( nVCLLut[ 255-nResAlpha ] + nD ) >> 16 ] ) );
1500 pAlphaW->SetPixelOnData( pScanlineAlpha, nX, aIndex );
1504 pB.reset();
1505 pW.reset();
1506 res = aDither;
1508 else
1510 BitmapScopedWriteAccess pB(aBmp);
1511 if (pB && pP && pA && pAlphaW)
1513 for( nY = 0; nY < nDstHeight; nY++ )
1515 const long nMapY = pMapY[ nY ];
1516 Scanline pScanlineB = pB->GetScanline(nY);
1517 Scanline pScanlineAlpha = pAlphaW->GetScanline(nY);
1519 for( nX = 0; nX < nDstWidth; nX++ )
1521 const long nMapX = pMapX[ nX ];
1522 aDstCol = AlphaBlend( nX, nY, nMapX, nMapY, pP, pA, pB.get(), pAlphaW.get(), nResAlpha );
1524 pB->SetPixelOnData(pScanlineB, nX, pB->GetBestMatchingColor(aDstCol));
1525 pAlphaW->SetPixelOnData(pScanlineAlpha, nX, pB->GetBestMatchingColor(Color(255L-nResAlpha, 255L-nResAlpha, 255L-nResAlpha)));
1529 pB.reset();
1530 res = aBmp;
1533 pAlphaW.reset();
1534 mpAlphaVDev->DrawBitmap( aDstRect.TopLeft(), aAlphaBitmap );
1535 mpAlphaVDev->EnableMapMode( bOldMapMode );
1537 return res;
1540 Bitmap OutputDevice::BlendBitmap(
1541 Bitmap& aBmp,
1542 BitmapReadAccess const * pP,
1543 BitmapReadAccess const * pA,
1544 const sal_Int32 nOffY,
1545 const sal_Int32 nDstHeight,
1546 const sal_Int32 nOffX,
1547 const sal_Int32 nDstWidth,
1548 const tools::Rectangle& aBmpRect,
1549 const Size& aOutSz,
1550 const bool bHMirr,
1551 const bool bVMirr,
1552 const long* pMapX,
1553 const long* pMapY )
1555 BitmapColor aDstCol;
1556 Bitmap res;
1557 int nX, nY;
1559 if( GetBitCount() <= 8 )
1561 Bitmap aDither( aBmp.GetSizePixel(), 8 );
1562 BitmapColor aIndex( 0 );
1563 Bitmap::ScopedReadAccess pB(aBmp);
1564 BitmapScopedWriteAccess pW(aDither);
1566 if( pB && pP && pA && pW )
1568 int nOutY;
1570 for( nY = 0, nOutY = nOffY; nY < nDstHeight; nY++, nOutY++ )
1572 long nMapY = pMapY[ nY ];
1573 if (bVMirr)
1575 nMapY = aBmpRect.Bottom() - nMapY;
1577 const long nModY = ( nOutY & 0x0FL ) << 4;
1578 int nOutX;
1580 Scanline pScanline = pW->GetScanline(nY);
1581 Scanline pScanlineAlpha = pA->GetScanline(nMapY);
1582 for( nX = 0, nOutX = nOffX; nX < nDstWidth; nX++, nOutX++ )
1584 long nMapX = pMapX[ nX ];
1585 if (bHMirr)
1587 nMapX = aBmpRect.Right() - nMapX;
1589 const sal_uLong nD = nVCLDitherLut[ nModY | ( nOutX & 0x0FL ) ];
1591 aDstCol = pB->GetColor( nY, nX );
1592 aDstCol.Merge( pP->GetColor( nMapY, nMapX ), pA->GetIndexFromData( pScanlineAlpha, nMapX ) );
1593 aIndex.SetIndex( static_cast<sal_uInt8>( nVCLRLut[ ( nVCLLut[ aDstCol.GetRed() ] + nD ) >> 16 ] +
1594 nVCLGLut[ ( nVCLLut[ aDstCol.GetGreen() ] + nD ) >> 16 ] +
1595 nVCLBLut[ ( nVCLLut[ aDstCol.GetBlue() ] + nD ) >> 16 ] ) );
1596 pW->SetPixelOnData( pScanline, nX, aIndex );
1601 pB.reset();
1602 pW.reset();
1603 res = aDither;
1605 else
1607 BitmapScopedWriteAccess pB(aBmp);
1609 bool bFastBlend = false;
1610 if( pP && pA && pB && !bHMirr && !bVMirr )
1612 SalTwoRect aTR(aBmpRect.Left(), aBmpRect.Top(), aBmpRect.GetWidth(), aBmpRect.GetHeight(),
1613 nOffX, nOffY, aOutSz.Width(), aOutSz.Height());
1615 bFastBlend = ImplFastBitmapBlending( *pB,*pP,*pA, aTR );
1618 if( pP && pA && pB && !bFastBlend )
1620 switch( pP->GetScanlineFormat() )
1622 case ScanlineFormat::N8BitPal:
1624 for( nY = 0; nY < nDstHeight; nY++ )
1626 long nMapY = pMapY[ nY ];
1627 if ( bVMirr )
1629 nMapY = aBmpRect.Bottom() - nMapY;
1631 Scanline pPScan = pP->GetScanline( nMapY );
1632 Scanline pAScan = pA->GetScanline( nMapY );
1633 Scanline pBScan = pB->GetScanline( nY );
1635 for( nX = 0; nX < nDstWidth; nX++ )
1637 long nMapX = pMapX[ nX ];
1639 if ( bHMirr )
1641 nMapX = aBmpRect.Right() - nMapX;
1643 aDstCol = pB->GetPixelFromData( pBScan, nX );
1644 aDstCol.Merge( pP->GetPaletteColor( pPScan[ nMapX ] ), pAScan[ nMapX ] );
1645 pB->SetPixelOnData( pBScan, nX, aDstCol );
1649 break;
1651 default:
1654 for( nY = 0; nY < nDstHeight; nY++ )
1656 long nMapY = pMapY[ nY ];
1658 if ( bVMirr )
1660 nMapY = aBmpRect.Bottom() - nMapY;
1662 Scanline pAScan = pA->GetScanline( nMapY );
1663 Scanline pBScan = pB->GetScanline(nY);
1664 for( nX = 0; nX < nDstWidth; nX++ )
1666 long nMapX = pMapX[ nX ];
1668 if ( bHMirr )
1670 nMapX = aBmpRect.Right() - nMapX;
1672 aDstCol = pB->GetPixelFromData( pBScan, nX );
1673 aDstCol.Merge( pP->GetColor( nMapY, nMapX ), pAScan[ nMapX ] );
1674 pB->SetPixelOnData( pBScan, nX, aDstCol );
1678 break;
1682 pB.reset();
1683 res = aBmp;
1686 return res;
1689 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */