1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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 <sal/config.h>
22 #include <tools/gen.hxx>
23 #include <vcl/outdev.hxx>
24 #include <vcl/alpha.hxx>
25 #include <vcl/virdev.hxx>
26 #include <vcl/GraphicObject.hxx>
31 ImplTileInfo() : nTilesEmptyX(0), nTilesEmptyY(0) {}
33 Point aTileTopLeft
; // top, left position of the rendered tile
34 Point aNextTileTopLeft
; // top, left position for next recursion
36 Size aTileSizePixel
; // size of the generated tile (might
38 // aNextTileTopLeft-aTileTopLeft, because
39 // this is nExponent*prevTileSize. The
40 // generated tile is always nExponent
41 // times the previous tile, such that it
42 // can be used in the next stage. The
43 // required area coverage is often
44 // less. The extraneous area covered is
45 // later overwritten by the next stage)
46 int nTilesEmptyX
; // number of original tiles empty right of
47 // this tile. This counts from
48 // aNextTileTopLeft, i.e. the additional
49 // area covered by aTileSizePixel is not
50 // considered here. This is for
51 // unification purposes, as the iterative
52 // calculation of the next level's empty
53 // tiles has to be based on this value.
54 int nTilesEmptyY
; // as above, for Y
58 bool GraphicObject::ImplRenderTempTile( VirtualDevice
& rVDev
,
59 int nNumTilesX
, int nNumTilesY
,
60 const Size
& rTileSizePixel
,
61 const GraphicAttr
* pAttr
)
63 // how many tiles to generate per recursion step
64 const int nExponent
= 2;
66 // determine MSB factor
68 while( nNumTilesX
/ nMSBFactor
!= 0 ||
69 nNumTilesY
/ nMSBFactor
!= 0 )
71 nMSBFactor
*= nExponent
;
77 nMSBFactor
/= nExponent
;
79 ImplTileInfo aTileInfo
;
81 // #105229# Switch off mapping (converting to logic and back to
82 // pixel might cause roundoff errors)
83 bool bOldMap( rVDev
.IsMapModeEnabled() );
84 rVDev
.EnableMapMode( false );
86 bool bRet( ImplRenderTileRecursive( rVDev
, nExponent
, nMSBFactor
, nNumTilesX
, nNumTilesY
,
87 nNumTilesX
, nNumTilesY
, rTileSizePixel
, pAttr
, aTileInfo
) );
89 rVDev
.EnableMapMode( bOldMap
);
94 // define for debug drawings
97 // see header comment. this works similar to base conversion of a
98 // number, i.e. if the exponent is 10, then the number for every tile
99 // size is given by the decimal place of the corresponding decimal
101 bool GraphicObject::ImplRenderTileRecursive( VirtualDevice
& rVDev
, int nExponent
, int nMSBFactor
,
102 int nNumOrigTilesX
, int nNumOrigTilesY
,
103 int nRemainderTilesX
, int nRemainderTilesY
,
104 const Size
& rTileSizePixel
, const GraphicAttr
* pAttr
,
105 ImplTileInfo
& rTileInfo
)
107 // gets loaded with our tile bitmap
108 std::unique_ptr
<GraphicObject
> xTmpGraphic
;
109 GraphicObject
* pTileGraphic
;
111 // stores a flag that renders the zero'th tile position
112 // (i.e. (0,0)+rCurrPos) only if we're at the bottom of the
113 // recursion stack. All other position already have that tile
114 // rendered, because the lower levels painted their generated tile
116 bool bNoFirstTileDraw( false );
118 // what's left when we're done with our tile size
119 const int nNewRemainderX( nRemainderTilesX
% nMSBFactor
);
120 const int nNewRemainderY( nRemainderTilesY
% nMSBFactor
);
122 // gets filled out from the recursive call with info of what's
124 ImplTileInfo aTileInfo
;
126 // check for recursion's end condition: LSB place reached?
127 if( nMSBFactor
== 1 )
131 // set initial tile size -> orig size
132 aTileInfo
.aTileSizePixel
= rTileSizePixel
;
133 aTileInfo
.nTilesEmptyX
= nNumOrigTilesX
;
134 aTileInfo
.nTilesEmptyY
= nNumOrigTilesY
;
136 else if( ImplRenderTileRecursive( rVDev
, nExponent
, nMSBFactor
/nExponent
,
137 nNumOrigTilesX
, nNumOrigTilesY
,
138 nNewRemainderX
, nNewRemainderY
,
139 rTileSizePixel
, pAttr
, aTileInfo
) )
141 // extract generated tile -> see comment on the first loop below
142 BitmapEx
aTileBitmap( rVDev
.GetBitmap( aTileInfo
.aTileTopLeft
, aTileInfo
.aTileSizePixel
) );
144 xTmpGraphic
.reset(new GraphicObject(std::move(aTileBitmap
)));
145 pTileGraphic
= xTmpGraphic
.get();
147 // fill stripes left over from upstream levels:
155 // where x denotes the place filled by our recursive predecessors
157 // check whether we have to fill stripes here. Although not
158 // obvious, there is one case where we can skip this step: if
159 // the previous recursion level (the one who filled our
160 // aTileInfo) had zero area to fill, then there are no white
161 // stripes left, naturally. This happens if the digit
162 // associated to that level has a zero, and can be checked via
163 // aTileTopLeft==aNextTileTopLeft.
164 if( aTileInfo
.aTileTopLeft
!= aTileInfo
.aNextTileTopLeft
)
166 // now fill one row from aTileInfo.aNextTileTopLeft.X() all
167 // the way to the right
168 // current output position while drawing
169 Point
aCurrPos(aTileInfo
.aNextTileTopLeft
.X(), aTileInfo
.aTileTopLeft
.Y());
170 for (int nX
=0; nX
< aTileInfo
.nTilesEmptyX
; nX
+= nMSBFactor
)
172 if (!pTileGraphic
->Draw(rVDev
, aCurrPos
, aTileInfo
.aTileSizePixel
, pAttr
))
175 aCurrPos
.AdjustX(aTileInfo
.aTileSizePixel
.Width() );
179 // rVDev.SetFillCOL_WHITE );
180 rVDev
.SetFillColor();
181 rVDev
.SetLineColor( Color( 255 * nExponent
/ nMSBFactor
, 255 - 255 * nExponent
/ nMSBFactor
, 128 - 255 * nExponent
/ nMSBFactor
) );
182 rVDev
.DrawEllipse( tools::Rectangle(aTileInfo
.aNextTileTopLeft
.X(), aTileInfo
.aTileTopLeft
.Y(),
183 aTileInfo
.aNextTileTopLeft
.X() - 1 + (aTileInfo
.nTilesEmptyX
/nMSBFactor
)*aTileInfo
.aTileSizePixel
.Width(),
184 aTileInfo
.aTileTopLeft
.Y() + aTileInfo
.aTileSizePixel
.Height() - 1) );
187 // now fill one column from aTileInfo.aNextTileTopLeft.Y() all
188 // the way to the bottom
189 aCurrPos
.setX( aTileInfo
.aTileTopLeft
.X() );
190 aCurrPos
.setY( aTileInfo
.aNextTileTopLeft
.Y() );
191 for (int nY
=0; nY
< aTileInfo
.nTilesEmptyY
; nY
+= nMSBFactor
)
193 if (!pTileGraphic
->Draw(rVDev
, aCurrPos
, aTileInfo
.aTileSizePixel
, pAttr
))
196 aCurrPos
.AdjustY(aTileInfo
.aTileSizePixel
.Height() );
200 rVDev
.DrawEllipse( tools::Rectangle(aTileInfo
.aTileTopLeft
.X(), aTileInfo
.aNextTileTopLeft
.Y(),
201 aTileInfo
.aTileTopLeft
.X() + aTileInfo
.aTileSizePixel
.Width() - 1,
202 aTileInfo
.aNextTileTopLeft
.Y() - 1 + (aTileInfo
.nTilesEmptyY
/nMSBFactor
)*aTileInfo
.aTileSizePixel
.Height()) );
207 // Thought that aTileInfo.aNextTileTopLeft tile has always
208 // been drawn already, but that's wrong: typically,
209 // _parts_ of that tile have been drawn, since the
210 // previous level generated the tile there. But when
211 // aTileInfo.aNextTileTopLeft!=aTileInfo.aTileTopLeft, the
212 // difference between these two values is missing in the
213 // lower right corner of this first tile. So, can do that
215 bNoFirstTileDraw
= true;
223 // calc number of original tiles in our drawing area without
225 nRemainderTilesX
-= nNewRemainderX
;
226 nRemainderTilesY
-= nNewRemainderY
;
228 // fill tile info for calling method
229 rTileInfo
.aTileTopLeft
= aTileInfo
.aNextTileTopLeft
;
230 rTileInfo
.aNextTileTopLeft
= Point( rTileInfo
.aTileTopLeft
.X() + rTileSizePixel
.Width()*nRemainderTilesX
,
231 rTileInfo
.aTileTopLeft
.Y() + rTileSizePixel
.Height()*nRemainderTilesY
);
232 rTileInfo
.aTileSizePixel
= Size( rTileSizePixel
.Width()*nMSBFactor
*nExponent
,
233 rTileSizePixel
.Height()*nMSBFactor
*nExponent
);
234 rTileInfo
.nTilesEmptyX
= aTileInfo
.nTilesEmptyX
- nRemainderTilesX
;
235 rTileInfo
.nTilesEmptyY
= aTileInfo
.nTilesEmptyY
- nRemainderTilesY
;
237 // init output position
238 Point aCurrPos
= aTileInfo
.aNextTileTopLeft
;
240 // fill our drawing area. Fill possibly more, to create the next
241 // bigger tile size -> see bitmap extraction above. This does no
242 // harm, since everything right or below our actual area is
243 // overdrawn by our caller. Just in case we're in the last level,
244 // we don't draw beyond the right or bottom border.
245 for (int nY
=0; nY
< aTileInfo
.nTilesEmptyY
&& nY
< nExponent
*nMSBFactor
; nY
+= nMSBFactor
)
247 aCurrPos
.setX( aTileInfo
.aNextTileTopLeft
.X() );
249 for (int nX
=0; nX
< aTileInfo
.nTilesEmptyX
&& nX
< nExponent
*nMSBFactor
; nX
+= nMSBFactor
)
251 if( bNoFirstTileDraw
)
252 bNoFirstTileDraw
= false; // don't draw first tile position
253 else if (!pTileGraphic
->Draw(rVDev
, aCurrPos
, aTileInfo
.aTileSizePixel
, pAttr
))
256 aCurrPos
.AdjustX(aTileInfo
.aTileSizePixel
.Width() );
259 aCurrPos
.AdjustY(aTileInfo
.aTileSizePixel
.Height() );
263 // rVDev.SetFillCOL_WHITE );
264 rVDev
.SetFillColor();
265 rVDev
.SetLineColor( Color( 255 * nExponent
/ nMSBFactor
, 255 - 255 * nExponent
/ nMSBFactor
, 128 - 255 * nExponent
/ nMSBFactor
) );
266 rVDev
.DrawRect( tools::Rectangle((rTileInfo
.aTileTopLeft
.X())*rTileSizePixel
.Width(),
267 (rTileInfo
.aTileTopLeft
.Y())*rTileSizePixel
.Height(),
268 (rTileInfo
.aNextTileTopLeft
.X())*rTileSizePixel
.Width()-1,
269 (rTileInfo
.aNextTileTopLeft
.Y())*rTileSizePixel
.Height()-1) );
275 bool GraphicObject::ImplDrawTiled(OutputDevice
& rOut
, const tools::Rectangle
& rArea
, const Size
& rSizePixel
,
276 const Size
& rOffset
, const GraphicAttr
* pAttr
, int nTileCacheSize1D
)
278 const MapMode
aOutMapMode(rOut
.GetMapMode());
279 const MapMode
aMapMode( aOutMapMode
.GetMapUnit(), Point(), aOutMapMode
.GetScaleX(), aOutMapMode
.GetScaleY() );
282 // #i42643# Casting to Int64, to avoid integer overflow for
283 // huge-DPI output devices
284 if( GetGraphic().GetType() == GraphicType::Bitmap
&&
285 static_cast<sal_Int64
>(rSizePixel
.Width()) * rSizePixel
.Height() <
286 static_cast<sal_Int64
>(nTileCacheSize1D
)*nTileCacheSize1D
)
288 // First combine very small bitmaps into a larger tile
291 ScopedVclPtrInstance
< VirtualDevice
> aVDev
;
292 const int nNumTilesInCacheX( (nTileCacheSize1D
+ rSizePixel
.Width()-1) / rSizePixel
.Width() );
293 const int nNumTilesInCacheY( (nTileCacheSize1D
+ rSizePixel
.Height()-1) / rSizePixel
.Height() );
295 aVDev
->SetOutputSizePixel( Size( nNumTilesInCacheX
*rSizePixel
.Width(),
296 nNumTilesInCacheY
*rSizePixel
.Height() ) );
297 aVDev
->SetMapMode( aMapMode
);
299 // draw bitmap content
300 if( ImplRenderTempTile( *aVDev
, nNumTilesInCacheX
,
301 nNumTilesInCacheY
, rSizePixel
, pAttr
) )
303 BitmapEx
aTileBitmap( aVDev
->GetBitmap( Point(0,0), aVDev
->GetOutputSize() ) );
305 // draw alpha content, if any
306 if( IsTransparent() )
308 GraphicObject aAlphaGraphic
;
310 if( GetGraphic().IsAlpha() )
311 aAlphaGraphic
.SetGraphic(BitmapEx(GetGraphic().GetBitmapEx().GetAlphaMask().GetBitmap()));
313 aAlphaGraphic
.SetGraphic(BitmapEx(Bitmap()));
315 if( aAlphaGraphic
.ImplRenderTempTile( *aVDev
, nNumTilesInCacheX
,
316 nNumTilesInCacheY
, rSizePixel
, pAttr
) )
318 // Combine bitmap and alpha/mask
319 if( GetGraphic().IsAlpha() )
320 aTileBitmap
= BitmapEx( aTileBitmap
.GetBitmap(),
321 AlphaMask( aVDev
->GetBitmap( Point(0,0), aVDev
->GetOutputSize() ) ) );
323 aTileBitmap
= BitmapEx( aTileBitmap
.GetBitmap(),
324 aVDev
->GetBitmap( Point(0,0), aVDev
->GetOutputSize() ).CreateAlphaMask( COL_WHITE
) );
328 // paint generated tile
329 GraphicObject
aTmpGraphic( aTileBitmap
);
330 bRet
= aTmpGraphic
.ImplDrawTiled(rOut
, rArea
,
331 aTileBitmap
.GetSizePixel(),
332 rOffset
, pAttr
, nTileCacheSize1D
);
337 const Size
aOutOffset( rOut
.LogicToPixel( rOffset
, aOutMapMode
) );
338 const tools::Rectangle
aOutArea( rOut
.LogicToPixel( rArea
, aOutMapMode
) );
340 // number of invisible (because out-of-area) tiles
341 int nInvisibleTilesX
;
342 int nInvisibleTilesY
;
344 // round towards -infty for negative offset
345 if( aOutOffset
.Width() < 0 )
346 nInvisibleTilesX
= (aOutOffset
.Width() - rSizePixel
.Width() + 1) / rSizePixel
.Width();
348 nInvisibleTilesX
= aOutOffset
.Width() / rSizePixel
.Width();
350 // round towards -infty for negative offset
351 if( aOutOffset
.Height() < 0 )
352 nInvisibleTilesY
= (aOutOffset
.Height() - rSizePixel
.Height() + 1) / rSizePixel
.Height();
354 nInvisibleTilesY
= aOutOffset
.Height() / rSizePixel
.Height();
356 // origin from where to 'virtually' start drawing in pixel
357 const Point
aOutOrigin( rOut
.LogicToPixel( Point( rArea
.Left() - rOffset
.Width(),
358 rArea
.Top() - rOffset
.Height() ) ) );
359 // position in pixel from where to really start output
360 const Point
aOutStart( aOutOrigin
.X() + nInvisibleTilesX
*rSizePixel
.Width(),
361 aOutOrigin
.Y() + nInvisibleTilesY
*rSizePixel
.Height() );
363 rOut
.Push( vcl::PushFlags::CLIPREGION
);
364 rOut
.IntersectClipRegion( rArea
);
367 auto nOutAreaWidth
= aOutArea
.GetWidth();
368 auto nOutAreaHeight
= aOutArea
.GetHeight();
369 assert(nOutAreaWidth
>= 0 && nOutAreaHeight
>= 0 && "coverity 2023.12.2");
370 bRet
= ImplDrawTiled(rOut
, aOutStart
,
371 (nOutAreaWidth
+ aOutArea
.Left() - aOutStart
.X() + rSizePixel
.Width() - 1) / rSizePixel
.Width(),
372 (nOutAreaHeight
+ aOutArea
.Top() - aOutStart
.Y() + rSizePixel
.Height() - 1) / rSizePixel
.Height(),
381 bool GraphicObject::ImplDrawTiled( OutputDevice
& rOut
, const Point
& rPosPixel
,
382 int nNumTilesX
, int nNumTilesY
,
383 const Size
& rTileSizePixel
, const GraphicAttr
* pAttr
) const
385 Point
aCurrPos( rPosPixel
);
386 Size
aTileSizeLogic( rOut
.PixelToLogic( rTileSizePixel
) );
389 // #107607# Use logical coordinates for metafile playing, too
390 bool bDrawInPixel( rOut
.GetConnectMetaFile() == nullptr && GraphicType::Bitmap
== GetType() );
393 // #105229# Switch off mapping (converting to logic and back to
394 // pixel might cause roundoff errors)
395 bool bOldMap( rOut
.IsMapModeEnabled() );
398 rOut
.EnableMapMode( false );
400 for( nY
=0; nY
< nNumTilesY
; ++nY
)
402 aCurrPos
.setX( rPosPixel
.X() );
404 for( nX
=0; nX
< nNumTilesX
; ++nX
)
406 // #105229# work with pixel coordinates here, mapping is disabled!
407 // #104004# don't disable mapping for metafile recordings
408 // #108412# don't quit the loop if one draw fails
410 // update return value. This method should return true, if
411 // at least one of the looped Draws succeeded.
413 bDrawInPixel
? aCurrPos
: rOut
.PixelToLogic(aCurrPos
),
414 bDrawInPixel
? rTileSizePixel
: aTileSizeLogic
,
417 aCurrPos
.AdjustX(rTileSizePixel
.Width() );
420 aCurrPos
.AdjustY(rTileSizePixel
.Height() );
424 rOut
.EnableMapMode( bOldMap
);
429 void GraphicObject::ImplTransformBitmap( BitmapEx
& rBmpEx
,
430 const GraphicAttr
& rAttr
,
431 const Size
& rCropLeftTop
,
432 const Size
& rCropRightBottom
,
433 const tools::Rectangle
& rCropRect
,
434 const Size
& rDstSize
,
435 bool bEnlarge
) const
437 // #107947# Extracted from svdograf.cxx
439 // #104115# Crop the bitmap
440 if( rAttr
.IsCropped() )
442 rBmpEx
.Crop( rCropRect
);
444 // #104115# Negative crop sizes mean: enlarge bitmap and pad
446 rCropLeftTop
.Width() < 0 ||
447 rCropLeftTop
.Height() < 0 ||
448 rCropRightBottom
.Width() < 0 ||
449 rCropRightBottom
.Height() < 0 ) )
451 Size
aBmpSize( rBmpEx
.GetSizePixel() );
452 sal_Int32
nPadLeft( rCropLeftTop
.Width() < 0 ? -rCropLeftTop
.Width() : 0 );
453 sal_Int32
nPadTop( rCropLeftTop
.Height() < 0 ? -rCropLeftTop
.Height() : 0 );
454 sal_Int32
nPadTotalWidth( aBmpSize
.Width() + nPadLeft
+ (rCropRightBottom
.Width() < 0 ? -rCropRightBottom
.Width() : 0) );
455 sal_Int32
nPadTotalHeight( aBmpSize
.Height() + nPadTop
+ (rCropRightBottom
.Height() < 0 ? -rCropRightBottom
.Height() : 0) );
459 if( rBmpEx
.IsAlpha() )
465 // #104115# Generate mask bitmap and init to zero
466 AlphaMask
aMask(aBmpSize
);
469 // #104115# Always generate transparent bitmap, we need the border transparent
470 aBmpEx2
= BitmapEx( rBmpEx
.GetBitmap(), aMask
);
472 // #104115# Add opaque mask to source bitmap, otherwise the destination remains transparent
476 aBmpEx2
.Scale(Size(nPadTotalWidth
, nPadTotalHeight
));
477 aBmpEx2
.Erase( Color(ColorAlpha
,0,0,0,0) );
478 aBmpEx2
.CopyPixel( tools::Rectangle( Point(nPadLeft
, nPadTop
), aBmpSize
), tools::Rectangle( Point(0, 0), aBmpSize
), rBmpEx
);
483 const Size
aSizePixel( rBmpEx
.GetSizePixel() );
485 if( rAttr
.GetRotation() == 0_deg10
|| IsAnimated() )
488 if( !(aSizePixel
.Width() && aSizePixel
.Height() && rDstSize
.Width() && rDstSize
.Height()) )
491 double fSrcWH
= static_cast<double>(aSizePixel
.Width()) / aSizePixel
.Height();
492 double fDstWH
= static_cast<double>(rDstSize
.Width()) / rDstSize
.Height();
493 double fScaleX
= 1.0, fScaleY
= 1.0;
495 // always choose scaling to shrink bitmap
496 if( fSrcWH
< fDstWH
)
497 fScaleY
= aSizePixel
.Width() / ( fDstWH
* aSizePixel
.Height() );
499 fScaleX
= fDstWH
* aSizePixel
.Height() / aSizePixel
.Width();
501 rBmpEx
.Scale( fScaleX
, fScaleY
);
504 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */