/src/libreoffice/vcl/source/graphic/GraphicObject2.cxx
Line | Count | Source (jump to first uncovered line) |
1 | | /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
2 | | /* |
3 | | * This file is part of the LibreOffice project. |
4 | | * |
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/. |
8 | | * |
9 | | * This file incorporates work covered by the following license notice: |
10 | | * |
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 . |
18 | | */ |
19 | | |
20 | | #include <sal/config.h> |
21 | | |
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> |
27 | | #include <memory> |
28 | | |
29 | | struct ImplTileInfo |
30 | | { |
31 | 0 | ImplTileInfo() : nTilesEmptyX(0), nTilesEmptyY(0) {} |
32 | | |
33 | | Point aTileTopLeft; // top, left position of the rendered tile |
34 | | Point aNextTileTopLeft; // top, left position for next recursion |
35 | | // level's tile |
36 | | Size aTileSizePixel; // size of the generated tile (might |
37 | | // differ from |
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 |
55 | | }; |
56 | | |
57 | | |
58 | | bool GraphicObject::ImplRenderTempTile( VirtualDevice& rVDev, |
59 | | int nNumTilesX, int nNumTilesY, |
60 | | const Size& rTileSizePixel, |
61 | | const GraphicAttr* pAttr ) |
62 | 0 | { |
63 | | // how many tiles to generate per recursion step |
64 | 0 | const int nExponent = 2; |
65 | | |
66 | | // determine MSB factor |
67 | 0 | int nMSBFactor( 1 ); |
68 | 0 | while( nNumTilesX / nMSBFactor != 0 || |
69 | 0 | nNumTilesY / nMSBFactor != 0 ) |
70 | 0 | { |
71 | 0 | nMSBFactor *= nExponent; |
72 | 0 | } |
73 | | |
74 | | // one less |
75 | 0 | if(nMSBFactor > 1) |
76 | 0 | { |
77 | 0 | nMSBFactor /= nExponent; |
78 | 0 | } |
79 | 0 | ImplTileInfo aTileInfo; |
80 | | |
81 | | // #105229# Switch off mapping (converting to logic and back to |
82 | | // pixel might cause roundoff errors) |
83 | 0 | bool bOldMap( rVDev.IsMapModeEnabled() ); |
84 | 0 | rVDev.EnableMapMode( false ); |
85 | |
|
86 | 0 | bool bRet( ImplRenderTileRecursive( rVDev, nExponent, nMSBFactor, nNumTilesX, nNumTilesY, |
87 | 0 | nNumTilesX, nNumTilesY, rTileSizePixel, pAttr, aTileInfo ) ); |
88 | |
|
89 | 0 | rVDev.EnableMapMode( bOldMap ); |
90 | |
|
91 | 0 | return bRet; |
92 | 0 | } |
93 | | |
94 | | // define for debug drawings |
95 | | //#define DBG_TEST |
96 | | |
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 |
100 | | // representation. |
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 ) |
106 | 0 | { |
107 | | // gets loaded with our tile bitmap |
108 | 0 | std::unique_ptr<GraphicObject> xTmpGraphic; |
109 | 0 | GraphicObject* pTileGraphic; |
110 | | |
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 |
115 | | // there. |
116 | 0 | bool bNoFirstTileDraw( false ); |
117 | | |
118 | | // what's left when we're done with our tile size |
119 | 0 | const int nNewRemainderX( nRemainderTilesX % nMSBFactor ); |
120 | 0 | const int nNewRemainderY( nRemainderTilesY % nMSBFactor ); |
121 | | |
122 | | // gets filled out from the recursive call with info of what's |
123 | | // been generated |
124 | 0 | ImplTileInfo aTileInfo; |
125 | | |
126 | | // check for recursion's end condition: LSB place reached? |
127 | 0 | if( nMSBFactor == 1 ) |
128 | 0 | { |
129 | 0 | pTileGraphic = this; |
130 | | |
131 | | // set initial tile size -> orig size |
132 | 0 | aTileInfo.aTileSizePixel = rTileSizePixel; |
133 | 0 | aTileInfo.nTilesEmptyX = nNumOrigTilesX; |
134 | 0 | aTileInfo.nTilesEmptyY = nNumOrigTilesY; |
135 | 0 | } |
136 | 0 | else if( ImplRenderTileRecursive( rVDev, nExponent, nMSBFactor/nExponent, |
137 | 0 | nNumOrigTilesX, nNumOrigTilesY, |
138 | 0 | nNewRemainderX, nNewRemainderY, |
139 | 0 | rTileSizePixel, pAttr, aTileInfo ) ) |
140 | 0 | { |
141 | | // extract generated tile -> see comment on the first loop below |
142 | 0 | BitmapEx aTileBitmap( rVDev.GetBitmap( aTileInfo.aTileTopLeft, aTileInfo.aTileSizePixel ) ); |
143 | |
|
144 | 0 | xTmpGraphic.reset(new GraphicObject(std::move(aTileBitmap))); |
145 | 0 | pTileGraphic = xTmpGraphic.get(); |
146 | | |
147 | | // fill stripes left over from upstream levels: |
148 | | |
149 | | // x0000 |
150 | | // 0 |
151 | | // 0 |
152 | | // 0 |
153 | | // 0 |
154 | | |
155 | | // where x denotes the place filled by our recursive predecessors |
156 | | |
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 | 0 | if( aTileInfo.aTileTopLeft != aTileInfo.aNextTileTopLeft ) |
165 | 0 | { |
166 | | // now fill one row from aTileInfo.aNextTileTopLeft.X() all |
167 | | // the way to the right |
168 | | // current output position while drawing |
169 | 0 | Point aCurrPos(aTileInfo.aNextTileTopLeft.X(), aTileInfo.aTileTopLeft.Y()); |
170 | 0 | for (int nX=0; nX < aTileInfo.nTilesEmptyX; nX += nMSBFactor) |
171 | 0 | { |
172 | 0 | if (!pTileGraphic->Draw(rVDev, aCurrPos, aTileInfo.aTileSizePixel, pAttr)) |
173 | 0 | return false; |
174 | | |
175 | 0 | aCurrPos.AdjustX(aTileInfo.aTileSizePixel.Width() ); |
176 | 0 | } |
177 | | |
178 | | #ifdef DBG_TEST |
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) ); |
185 | | #endif |
186 | | |
187 | | // now fill one column from aTileInfo.aNextTileTopLeft.Y() all |
188 | | // the way to the bottom |
189 | 0 | aCurrPos.setX( aTileInfo.aTileTopLeft.X() ); |
190 | 0 | aCurrPos.setY( aTileInfo.aNextTileTopLeft.Y() ); |
191 | 0 | for (int nY=0; nY < aTileInfo.nTilesEmptyY; nY += nMSBFactor) |
192 | 0 | { |
193 | 0 | if (!pTileGraphic->Draw(rVDev, aCurrPos, aTileInfo.aTileSizePixel, pAttr)) |
194 | 0 | return false; |
195 | | |
196 | 0 | aCurrPos.AdjustY(aTileInfo.aTileSizePixel.Height() ); |
197 | 0 | } |
198 | |
|
199 | | #ifdef DBG_TEST |
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()) ); |
203 | | #endif |
204 | 0 | } |
205 | 0 | else |
206 | 0 | { |
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 |
214 | | // only here. |
215 | 0 | bNoFirstTileDraw = true; |
216 | 0 | } |
217 | 0 | } |
218 | 0 | else |
219 | 0 | { |
220 | 0 | return false; |
221 | 0 | } |
222 | | |
223 | | // calc number of original tiles in our drawing area without |
224 | | // remainder |
225 | 0 | nRemainderTilesX -= nNewRemainderX; |
226 | 0 | nRemainderTilesY -= nNewRemainderY; |
227 | | |
228 | | // fill tile info for calling method |
229 | 0 | rTileInfo.aTileTopLeft = aTileInfo.aNextTileTopLeft; |
230 | 0 | rTileInfo.aNextTileTopLeft = Point( rTileInfo.aTileTopLeft.X() + rTileSizePixel.Width()*nRemainderTilesX, |
231 | 0 | rTileInfo.aTileTopLeft.Y() + rTileSizePixel.Height()*nRemainderTilesY ); |
232 | 0 | rTileInfo.aTileSizePixel = Size( rTileSizePixel.Width()*nMSBFactor*nExponent, |
233 | 0 | rTileSizePixel.Height()*nMSBFactor*nExponent ); |
234 | 0 | rTileInfo.nTilesEmptyX = aTileInfo.nTilesEmptyX - nRemainderTilesX; |
235 | 0 | rTileInfo.nTilesEmptyY = aTileInfo.nTilesEmptyY - nRemainderTilesY; |
236 | | |
237 | | // init output position |
238 | 0 | Point aCurrPos = aTileInfo.aNextTileTopLeft; |
239 | | |
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 | 0 | for (int nY=0; nY < aTileInfo.nTilesEmptyY && nY < nExponent*nMSBFactor; nY += nMSBFactor) |
246 | 0 | { |
247 | 0 | aCurrPos.setX( aTileInfo.aNextTileTopLeft.X() ); |
248 | |
|
249 | 0 | for (int nX=0; nX < aTileInfo.nTilesEmptyX && nX < nExponent*nMSBFactor; nX += nMSBFactor) |
250 | 0 | { |
251 | 0 | if( bNoFirstTileDraw ) |
252 | 0 | bNoFirstTileDraw = false; // don't draw first tile position |
253 | 0 | else if (!pTileGraphic->Draw(rVDev, aCurrPos, aTileInfo.aTileSizePixel, pAttr)) |
254 | 0 | return false; |
255 | | |
256 | 0 | aCurrPos.AdjustX(aTileInfo.aTileSizePixel.Width() ); |
257 | 0 | } |
258 | | |
259 | 0 | aCurrPos.AdjustY(aTileInfo.aTileSizePixel.Height() ); |
260 | 0 | } |
261 | | |
262 | | #ifdef DBG_TEST |
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) ); |
270 | | #endif |
271 | | |
272 | 0 | return true; |
273 | 0 | } |
274 | | |
275 | | bool GraphicObject::ImplDrawTiled(OutputDevice& rOut, const tools::Rectangle& rArea, const Size& rSizePixel, |
276 | | const Size& rOffset, const GraphicAttr* pAttr, int nTileCacheSize1D) |
277 | 0 | { |
278 | 0 | const MapMode aOutMapMode(rOut.GetMapMode()); |
279 | 0 | const MapMode aMapMode( aOutMapMode.GetMapUnit(), Point(), aOutMapMode.GetScaleX(), aOutMapMode.GetScaleY() ); |
280 | 0 | bool bRet( false ); |
281 | | |
282 | | // #i42643# Casting to Int64, to avoid integer overflow for |
283 | | // huge-DPI output devices |
284 | 0 | if( GetGraphic().GetType() == GraphicType::Bitmap && |
285 | 0 | static_cast<sal_Int64>(rSizePixel.Width()) * rSizePixel.Height() < |
286 | 0 | static_cast<sal_Int64>(nTileCacheSize1D)*nTileCacheSize1D ) |
287 | 0 | { |
288 | | // First combine very small bitmaps into a larger tile |
289 | | |
290 | |
|
291 | 0 | ScopedVclPtrInstance< VirtualDevice > aVDev; |
292 | 0 | const int nNumTilesInCacheX( (nTileCacheSize1D + rSizePixel.Width()-1) / rSizePixel.Width() ); |
293 | 0 | const int nNumTilesInCacheY( (nTileCacheSize1D + rSizePixel.Height()-1) / rSizePixel.Height() ); |
294 | |
|
295 | 0 | aVDev->SetOutputSizePixel( Size( nNumTilesInCacheX*rSizePixel.Width(), |
296 | 0 | nNumTilesInCacheY*rSizePixel.Height() ) ); |
297 | 0 | aVDev->SetMapMode( aMapMode ); |
298 | | |
299 | | // draw bitmap content |
300 | 0 | if( ImplRenderTempTile( *aVDev, nNumTilesInCacheX, |
301 | 0 | nNumTilesInCacheY, rSizePixel, pAttr ) ) |
302 | 0 | { |
303 | 0 | BitmapEx aTileBitmap( aVDev->GetBitmap( Point(0,0), aVDev->GetOutputSize() ) ); |
304 | | |
305 | | // draw alpha content, if any |
306 | 0 | if( IsTransparent() ) |
307 | 0 | { |
308 | 0 | GraphicObject aAlphaGraphic; |
309 | |
|
310 | 0 | if( GetGraphic().IsAlpha() ) |
311 | 0 | aAlphaGraphic.SetGraphic(BitmapEx(GetGraphic().GetBitmapEx().GetAlphaMask().GetBitmap())); |
312 | 0 | else |
313 | 0 | aAlphaGraphic.SetGraphic(BitmapEx(Bitmap())); |
314 | |
|
315 | 0 | if( aAlphaGraphic.ImplRenderTempTile( *aVDev, nNumTilesInCacheX, |
316 | 0 | nNumTilesInCacheY, rSizePixel, pAttr ) ) |
317 | 0 | { |
318 | | // Combine bitmap and alpha/mask |
319 | 0 | if( GetGraphic().IsAlpha() ) |
320 | 0 | aTileBitmap = BitmapEx( aTileBitmap.GetBitmap(), |
321 | 0 | AlphaMask( aVDev->GetBitmap( Point(0,0), aVDev->GetOutputSize() ) ) ); |
322 | 0 | else |
323 | 0 | aTileBitmap = BitmapEx( aTileBitmap.GetBitmap(), |
324 | 0 | aVDev->GetBitmap( Point(0,0), aVDev->GetOutputSize() ).CreateAlphaMask( COL_WHITE ) ); |
325 | 0 | } |
326 | 0 | } |
327 | | |
328 | | // paint generated tile |
329 | 0 | GraphicObject aTmpGraphic( aTileBitmap ); |
330 | 0 | bRet = aTmpGraphic.ImplDrawTiled(rOut, rArea, |
331 | 0 | aTileBitmap.GetSizePixel(), |
332 | 0 | rOffset, pAttr, nTileCacheSize1D); |
333 | 0 | } |
334 | 0 | } |
335 | 0 | else |
336 | 0 | { |
337 | 0 | const Size aOutOffset( rOut.LogicToPixel( rOffset, aOutMapMode ) ); |
338 | 0 | const tools::Rectangle aOutArea( rOut.LogicToPixel( rArea, aOutMapMode ) ); |
339 | | |
340 | | // number of invisible (because out-of-area) tiles |
341 | 0 | int nInvisibleTilesX; |
342 | 0 | int nInvisibleTilesY; |
343 | | |
344 | | // round towards -infty for negative offset |
345 | 0 | if( aOutOffset.Width() < 0 ) |
346 | 0 | nInvisibleTilesX = (aOutOffset.Width() - rSizePixel.Width() + 1) / rSizePixel.Width(); |
347 | 0 | else |
348 | 0 | nInvisibleTilesX = aOutOffset.Width() / rSizePixel.Width(); |
349 | | |
350 | | // round towards -infty for negative offset |
351 | 0 | if( aOutOffset.Height() < 0 ) |
352 | 0 | nInvisibleTilesY = (aOutOffset.Height() - rSizePixel.Height() + 1) / rSizePixel.Height(); |
353 | 0 | else |
354 | 0 | nInvisibleTilesY = aOutOffset.Height() / rSizePixel.Height(); |
355 | | |
356 | | // origin from where to 'virtually' start drawing in pixel |
357 | 0 | const Point aOutOrigin( rOut.LogicToPixel( Point( rArea.Left() - rOffset.Width(), |
358 | 0 | rArea.Top() - rOffset.Height() ) ) ); |
359 | | // position in pixel from where to really start output |
360 | 0 | const Point aOutStart( aOutOrigin.X() + nInvisibleTilesX*rSizePixel.Width(), |
361 | 0 | aOutOrigin.Y() + nInvisibleTilesY*rSizePixel.Height() ); |
362 | |
|
363 | 0 | rOut.Push( vcl::PushFlags::CLIPREGION ); |
364 | 0 | rOut.IntersectClipRegion( rArea ); |
365 | | |
366 | | // Paint all tiles |
367 | 0 | auto nOutAreaWidth = aOutArea.GetWidth(); |
368 | 0 | auto nOutAreaHeight = aOutArea.GetHeight(); |
369 | 0 | assert(nOutAreaWidth >= 0 && nOutAreaHeight >= 0 && "coverity 2023.12.2"); |
370 | 0 | bRet = ImplDrawTiled(rOut, aOutStart, |
371 | 0 | (nOutAreaWidth + aOutArea.Left() - aOutStart.X() + rSizePixel.Width() - 1) / rSizePixel.Width(), |
372 | 0 | (nOutAreaHeight + aOutArea.Top() - aOutStart.Y() + rSizePixel.Height() - 1) / rSizePixel.Height(), |
373 | 0 | rSizePixel, pAttr); |
374 | |
|
375 | 0 | rOut.Pop(); |
376 | 0 | } |
377 | |
|
378 | 0 | return bRet; |
379 | 0 | } |
380 | | |
381 | | bool GraphicObject::ImplDrawTiled( OutputDevice& rOut, const Point& rPosPixel, |
382 | | int nNumTilesX, int nNumTilesY, |
383 | | const Size& rTileSizePixel, const GraphicAttr* pAttr ) const |
384 | 0 | { |
385 | 0 | Point aCurrPos( rPosPixel ); |
386 | 0 | Size aTileSizeLogic( rOut.PixelToLogic( rTileSizePixel ) ); |
387 | 0 | int nX, nY; |
388 | | |
389 | | // #107607# Use logical coordinates for metafile playing, too |
390 | 0 | bool bDrawInPixel( rOut.GetConnectMetaFile() == nullptr && GraphicType::Bitmap == GetType() ); |
391 | 0 | bool bRet = false; |
392 | | |
393 | | // #105229# Switch off mapping (converting to logic and back to |
394 | | // pixel might cause roundoff errors) |
395 | 0 | bool bOldMap( rOut.IsMapModeEnabled() ); |
396 | |
|
397 | 0 | if( bDrawInPixel ) |
398 | 0 | rOut.EnableMapMode( false ); |
399 | |
|
400 | 0 | for( nY=0; nY < nNumTilesY; ++nY ) |
401 | 0 | { |
402 | 0 | aCurrPos.setX( rPosPixel.X() ); |
403 | |
|
404 | 0 | for( nX=0; nX < nNumTilesX; ++nX ) |
405 | 0 | { |
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 |
409 | | |
410 | | // update return value. This method should return true, if |
411 | | // at least one of the looped Draws succeeded. |
412 | 0 | bRet |= Draw(rOut, |
413 | 0 | bDrawInPixel ? aCurrPos : rOut.PixelToLogic(aCurrPos), |
414 | 0 | bDrawInPixel ? rTileSizePixel : aTileSizeLogic, |
415 | 0 | pAttr); |
416 | |
|
417 | 0 | aCurrPos.AdjustX(rTileSizePixel.Width() ); |
418 | 0 | } |
419 | |
|
420 | 0 | aCurrPos.AdjustY(rTileSizePixel.Height() ); |
421 | 0 | } |
422 | |
|
423 | 0 | if( bDrawInPixel ) |
424 | 0 | rOut.EnableMapMode( bOldMap ); |
425 | |
|
426 | 0 | return bRet; |
427 | 0 | } |
428 | | |
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 |
436 | 0 | { |
437 | | // #107947# Extracted from svdograf.cxx |
438 | | |
439 | | // #104115# Crop the bitmap |
440 | 0 | if( rAttr.IsCropped() ) |
441 | 0 | { |
442 | 0 | rBmpEx.Crop( rCropRect ); |
443 | | |
444 | | // #104115# Negative crop sizes mean: enlarge bitmap and pad |
445 | 0 | if( bEnlarge && ( |
446 | 0 | rCropLeftTop.Width() < 0 || |
447 | 0 | rCropLeftTop.Height() < 0 || |
448 | 0 | rCropRightBottom.Width() < 0 || |
449 | 0 | rCropRightBottom.Height() < 0 ) ) |
450 | 0 | { |
451 | 0 | Size aBmpSize( rBmpEx.GetSizePixel() ); |
452 | 0 | sal_Int32 nPadLeft( rCropLeftTop.Width() < 0 ? -rCropLeftTop.Width() : 0 ); |
453 | 0 | sal_Int32 nPadTop( rCropLeftTop.Height() < 0 ? -rCropLeftTop.Height() : 0 ); |
454 | 0 | sal_Int32 nPadTotalWidth( aBmpSize.Width() + nPadLeft + (rCropRightBottom.Width() < 0 ? -rCropRightBottom.Width() : 0) ); |
455 | 0 | sal_Int32 nPadTotalHeight( aBmpSize.Height() + nPadTop + (rCropRightBottom.Height() < 0 ? -rCropRightBottom.Height() : 0) ); |
456 | |
|
457 | 0 | BitmapEx aBmpEx2; |
458 | |
|
459 | 0 | if( rBmpEx.IsAlpha() ) |
460 | 0 | { |
461 | 0 | aBmpEx2 = rBmpEx; |
462 | 0 | } |
463 | 0 | else |
464 | 0 | { |
465 | | // #104115# Generate mask bitmap and init to zero |
466 | 0 | AlphaMask aMask(aBmpSize); |
467 | 0 | aMask.Erase(255); |
468 | | |
469 | | // #104115# Always generate transparent bitmap, we need the border transparent |
470 | 0 | aBmpEx2 = BitmapEx( rBmpEx.GetBitmap(), aMask ); |
471 | | |
472 | | // #104115# Add opaque mask to source bitmap, otherwise the destination remains transparent |
473 | 0 | rBmpEx = aBmpEx2; |
474 | 0 | } |
475 | |
|
476 | 0 | aBmpEx2.Scale(Size(nPadTotalWidth, nPadTotalHeight)); |
477 | 0 | aBmpEx2.Erase( Color(ColorAlpha,0,0,0,0) ); |
478 | 0 | aBmpEx2.CopyPixel( tools::Rectangle( Point(nPadLeft, nPadTop), aBmpSize ), tools::Rectangle( Point(0, 0), aBmpSize ), rBmpEx ); |
479 | 0 | rBmpEx = aBmpEx2; |
480 | 0 | } |
481 | 0 | } |
482 | |
|
483 | 0 | const Size aSizePixel( rBmpEx.GetSizePixel() ); |
484 | |
|
485 | 0 | if( rAttr.GetRotation() == 0_deg10 || IsAnimated() ) |
486 | 0 | return; |
487 | | |
488 | 0 | if( !(aSizePixel.Width() && aSizePixel.Height() && rDstSize.Width() && rDstSize.Height()) ) |
489 | 0 | return; |
490 | | |
491 | 0 | double fSrcWH = static_cast<double>(aSizePixel.Width()) / aSizePixel.Height(); |
492 | 0 | double fDstWH = static_cast<double>(rDstSize.Width()) / rDstSize.Height(); |
493 | 0 | double fScaleX = 1.0, fScaleY = 1.0; |
494 | | |
495 | | // always choose scaling to shrink bitmap |
496 | 0 | if( fSrcWH < fDstWH ) |
497 | 0 | fScaleY = aSizePixel.Width() / ( fDstWH * aSizePixel.Height() ); |
498 | 0 | else |
499 | 0 | fScaleX = fDstWH * aSizePixel.Height() / aSizePixel.Width(); |
500 | |
|
501 | 0 | rBmpEx.Scale( fScaleX, fScaleY ); |
502 | 0 | } |
503 | | |
504 | | /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |