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>
24 #include <drawinglayer/primitive2d/graphicprimitivehelper2d.hxx>
25 #include <drawinglayer/animation/animationtiming.hxx>
26 #include <drawinglayer/primitive2d/bitmapprimitive2d.hxx>
27 #include <drawinglayer/primitive2d/animatedprimitive2d.hxx>
28 #include <drawinglayer/primitive2d/metafileprimitive2d.hxx>
29 #include <drawinglayer/primitive2d/transformprimitive2d.hxx>
30 #include <drawinglayer/primitive2d/maskprimitive2d.hxx>
31 #include <drawinglayer/primitive2d/modifiedcolorprimitive2d.hxx>
32 #include <drawinglayer/geometry/viewinformation2d.hxx>
33 #include <basegfx/polygon/b2dpolygon.hxx>
34 #include <basegfx/polygon/b2dpolygontools.hxx>
35 #include <basegfx/numeric/ftools.hxx>
37 // helper class for animated graphics
39 #include <vcl/animate/Animation.hxx>
40 #include <vcl/graph.hxx>
41 #include <vcl/virdev.hxx>
42 #include <vcl/svapp.hxx>
43 #include <vcl/metaact.hxx>
45 namespace drawinglayer
49 class AnimatedGraphicPrimitive2D
: public AnimatedSwitchPrimitive2D
52 /// the geometric definition
53 basegfx::B2DHomMatrix maTransform
;
55 /** the Graphic with all its content possibilities, here only
56 animated is allowed and gets checked by isValidData().
57 an instance of Graphic is used here since it's ref-counted
58 and thus a safe copy for now
60 const Graphic maGraphic
;
62 /// local animation processing data, excerpt from maGraphic
63 ::Animation maAnimation
;
65 /// the on-demand created VirtualDevices for frame creation
66 ScopedVclPtrInstance
< VirtualDevice
> maVirtualDevice
;
67 ScopedVclPtrInstance
< VirtualDevice
> maVirtualDeviceMask
;
69 // index of the next frame that would be regularly prepared
70 sal_uInt32 mnNextFrameToPrepare
;
72 /// buffering of 1st frame (always active)
73 Primitive2DReference maBufferedFirstFrame
;
75 /// buffering of all frames
76 Primitive2DContainer maBufferedPrimitives
;
77 bool mbBufferingAllowed
;
79 /// set if the animation is huge so that just always the next frame
80 /// is used instead of using timing
84 bool isValidData() const
86 return (GraphicType::Bitmap
== maGraphic
.GetType()
87 && maGraphic
.IsAnimated()
88 && maAnimation
.Count());
91 void ensureVirtualDeviceSizeAndState()
95 const Size
aCurrent(maVirtualDevice
->GetOutputSizePixel());
96 const Size
aTarget(maAnimation
.GetDisplaySizePixel());
98 if (aCurrent
!= aTarget
)
100 maVirtualDevice
->EnableMapMode(false);
101 maVirtualDeviceMask
->EnableMapMode(false);
102 maVirtualDevice
->SetOutputSizePixel(aTarget
);
103 maVirtualDeviceMask
->SetOutputSizePixel(aTarget
);
106 maVirtualDevice
->Erase();
107 maVirtualDeviceMask
->Erase();
108 const ::tools::Rectangle
aRect(Point(0, 0), aTarget
);
109 maVirtualDeviceMask
->SetFillColor(COL_BLACK
);
110 maVirtualDeviceMask
->SetLineColor();
111 maVirtualDeviceMask
->DrawRect(aRect
);
115 sal_uInt32
generateStepTime(sal_uInt32 nIndex
) const
117 const AnimationBitmap
& rAnimationBitmap
= maAnimation
.Get(sal_uInt16(nIndex
));
118 sal_uInt32
nWaitTime(rAnimationBitmap
.mnWait
* 10);
120 // Take care of special value for MultiPage TIFFs. ATM these shall just
121 // show their first page. Later we will offer some switching when object
123 if (ANIMATION_TIMEOUT_ON_CLICK
== rAnimationBitmap
.mnWait
)
125 // ATM the huge value would block the timer, so
126 // use a long time to show first page (whole day)
127 nWaitTime
= 100 * 60 * 60 * 24;
130 // Bad trap: There are animated gifs with no set WaitTime (!).
131 // In that case use a default value.
140 void createAndSetAnimationTiming()
144 animation::AnimationEntryLoop
aAnimationLoop(maAnimation
.GetLoopCount() ? maAnimation
.GetLoopCount() : 0xffff);
145 const sal_uInt32
nCount(maAnimation
.Count());
147 for (sal_uInt32
a(0); a
< nCount
; a
++)
149 const sal_uInt32
aStepTime(generateStepTime(a
));
150 const animation::AnimationEntryFixed
aTime(static_cast<double>(aStepTime
), static_cast<double>(a
) / static_cast<double>(nCount
));
152 aAnimationLoop
.append(aTime
);
155 animation::AnimationEntryList aAnimationEntryList
;
156 aAnimationEntryList
.append(aAnimationLoop
);
158 setAnimationEntry(aAnimationEntryList
);
162 Primitive2DReference
createFromBuffer() const
164 // create BitmapEx by extracting from VirtualDevices
165 const Bitmap
aMainBitmap(maVirtualDevice
->GetBitmap(Point(), maVirtualDevice
->GetOutputSizePixel()));
166 #if defined(MACOSX) || defined(IOS)
167 const AlphaMask
aMaskBitmap(maVirtualDeviceMask
->GetBitmap(Point(), maVirtualDeviceMask
->GetOutputSizePixel()));
169 const Bitmap
aMaskBitmap(maVirtualDeviceMask
->GetBitmap(Point(), maVirtualDeviceMask
->GetOutputSizePixel()));
172 return Primitive2DReference(
173 new BitmapPrimitive2D(
174 BitmapEx(aMainBitmap
, aMaskBitmap
),
178 void checkSafeToBuffer(sal_uInt32 nIndex
)
180 if (mbBufferingAllowed
)
182 // all frames buffered
183 if (!maBufferedPrimitives
.empty() && nIndex
< maBufferedPrimitives
.size())
185 if (!maBufferedPrimitives
[nIndex
].is())
187 maBufferedPrimitives
[nIndex
] = createFromBuffer();
189 // check if buffering is complete
190 bool bBufferingComplete(true);
192 for (auto const & a
: maBufferedPrimitives
)
196 bBufferingComplete
= false;
201 if (bBufferingComplete
)
203 maVirtualDevice
.disposeAndClear();
204 maVirtualDeviceMask
.disposeAndClear();
211 // always buffer first frame
212 if (0 == nIndex
&& !maBufferedFirstFrame
.is())
214 maBufferedFirstFrame
= createFromBuffer();
219 void createFrame(sal_uInt32 nTarget
)
221 // mnNextFrameToPrepare is the target frame to create next (which implies that
222 // mnNextFrameToPrepare-1 *is* currently in the VirtualDevice when
223 // 0 != mnNextFrameToPrepare. nTarget is the traget frame.
226 if (mnNextFrameToPrepare
> nTarget
)
228 // we are ahead request, reset mechanism to start at frame zero
229 ensureVirtualDeviceSizeAndState();
230 mnNextFrameToPrepare
= 0;
233 while (mnNextFrameToPrepare
<= nTarget
)
236 const AnimationBitmap
& rAnimationBitmap
= maAnimation
.Get(sal_uInt16(mnNextFrameToPrepare
));
238 switch (rAnimationBitmap
.meDisposal
)
242 maVirtualDevice
->DrawBitmapEx(rAnimationBitmap
.maPositionPixel
, rAnimationBitmap
.maBitmapEx
);
243 Bitmap aMask
= rAnimationBitmap
.maBitmapEx
.GetMask();
248 const ::tools::Rectangle
aRect(aEmpty
, maVirtualDeviceMask
->GetOutputSizePixel());
249 const Wallpaper
aWallpaper(COL_BLACK
);
250 maVirtualDeviceMask
->DrawWallpaper(aRect
, aWallpaper
);
254 BitmapEx
aExpandVisibilityMask(aMask
, aMask
);
255 maVirtualDeviceMask
->DrawBitmapEx(rAnimationBitmap
.maPositionPixel
, aExpandVisibilityMask
);
262 // #i70772# react on no mask, for primitives, too.
263 const Bitmap
aMask(rAnimationBitmap
.maBitmapEx
.GetMask());
264 const Bitmap
aContent(rAnimationBitmap
.maBitmapEx
.GetBitmap());
266 maVirtualDeviceMask
->Erase();
267 maVirtualDevice
->DrawBitmap(rAnimationBitmap
.maPositionPixel
, aContent
);
271 const ::tools::Rectangle
aRect(rAnimationBitmap
.maPositionPixel
, aContent
.GetSizePixel());
272 maVirtualDeviceMask
->SetFillColor(COL_BLACK
);
273 maVirtualDeviceMask
->SetLineColor();
274 maVirtualDeviceMask
->DrawRect(aRect
);
278 BitmapEx
aExpandVisibilityMask(aMask
, aMask
);
279 maVirtualDeviceMask
->DrawBitmapEx(rAnimationBitmap
.maPositionPixel
, aExpandVisibilityMask
);
284 case Disposal::Previous
:
286 maVirtualDevice
->DrawBitmapEx(rAnimationBitmap
.maPositionPixel
, rAnimationBitmap
.maBitmapEx
);
287 BitmapEx
aExpandVisibilityMask(rAnimationBitmap
.maBitmapEx
.GetMask(), rAnimationBitmap
.maBitmapEx
.GetMask());
288 maVirtualDeviceMask
->DrawBitmapEx(rAnimationBitmap
.maPositionPixel
, aExpandVisibilityMask
);
293 // to not waste created data, check adding to buffers
294 checkSafeToBuffer(mnNextFrameToPrepare
);
296 mnNextFrameToPrepare
++;
301 Primitive2DReference
tryTogetFromBuffer(sal_uInt32 nIndex
) const
303 if (mbBufferingAllowed
)
305 // all frames buffered, check if available
306 if (!maBufferedPrimitives
.empty() && nIndex
< maBufferedPrimitives
.size())
308 if (maBufferedPrimitives
[nIndex
].is())
310 return maBufferedPrimitives
[nIndex
];
316 // always buffer first frame, it's sometimes requested out-of-order
317 if (0 == nIndex
&& maBufferedFirstFrame
.is())
319 return maBufferedFirstFrame
;
323 return Primitive2DReference();
328 AnimatedGraphicPrimitive2D(
329 const Graphic
& rGraphic
,
330 const basegfx::B2DHomMatrix
& rTransform
);
333 const Graphic
& getGraphic() const { return maGraphic
; }
334 const basegfx::B2DHomMatrix
& getTransform() const { return maTransform
; }
337 virtual bool operator==(const BasePrimitive2D
& rPrimitive
) const override
;
339 /// override to deliver the correct expected frame dependent of timing
340 virtual void get2DDecomposition(Primitive2DDecompositionVisitor
& rVisitor
, const geometry::ViewInformation2D
& rViewInformation
) const override
;
343 AnimatedGraphicPrimitive2D::AnimatedGraphicPrimitive2D(
344 const Graphic
& rGraphic
,
345 const basegfx::B2DHomMatrix
& rTransform
)
346 : AnimatedSwitchPrimitive2D(
347 animation::AnimationEntryList(),
348 Primitive2DContainer(),
350 maTransform(rTransform
),
352 maAnimation(rGraphic
.GetAnimation()),
353 maVirtualDevice(*Application::GetDefaultDevice()),
354 maVirtualDeviceMask(*Application::GetDefaultDevice(), DeviceFormat::BITMASK
),
355 mnNextFrameToPrepare(SAL_MAX_UINT32
),
356 maBufferedFirstFrame(),
357 maBufferedPrimitives(),
358 mbBufferingAllowed(false),
361 // initialize AnimationTiming, needed to detect which frame is requested
362 // in get2DDecomposition
363 createAndSetAnimationTiming();
365 // check if we allow buffering
368 // allow buffering up to a size of:
370 // - sizes of 256x256 pixels
371 // This may be offered in option values if needed
372 static const sal_uInt64
nAllowedSize(64 * 256 * 256);
373 static const sal_uInt64
nHugeSize(10000000);
374 const Size
aTarget(maAnimation
.GetDisplaySizePixel());
375 const sal_uInt64
nUsedSize(static_cast<sal_uInt64
>(maAnimation
.Count()) * aTarget
.Width() * aTarget
.Height());
377 if (nUsedSize
< nAllowedSize
)
379 mbBufferingAllowed
= true;
382 if (nUsedSize
> nHugeSize
)
388 // prepare buffer space
389 if (mbBufferingAllowed
&& isValidData())
391 maBufferedPrimitives
= Primitive2DContainer(maAnimation
.Count());
395 bool AnimatedGraphicPrimitive2D::operator==(const BasePrimitive2D
& rPrimitive
) const
397 // do not use 'GroupPrimitive2D::operator==' here, that would compare
398 // the children. Also do not use 'BasePrimitive2D::operator==', that would
399 // check the ID-Type. Since we are a simple derivation without own ID,
400 // use the dynamic_cast RTTI directly
401 const AnimatedGraphicPrimitive2D
* pCompare
= dynamic_cast<const AnimatedGraphicPrimitive2D
*>(&rPrimitive
);
403 // use operator== of Graphic - if that is equal, the basic definition is equal
404 return (nullptr != pCompare
405 && getTransform() == pCompare
->getTransform()
406 && getGraphic() == pCompare
->getGraphic());
409 void AnimatedGraphicPrimitive2D::get2DDecomposition(Primitive2DDecompositionVisitor
& rVisitor
, const geometry::ViewInformation2D
& rViewInformation
) const
413 Primitive2DReference aRetval
;
414 const double fState(getAnimationEntry().getStateAtTime(rViewInformation
.getViewTime()));
415 const sal_uInt32
nLen(maAnimation
.Count());
416 sal_uInt32
nIndex(basegfx::fround(fState
* static_cast<double>(nLen
)));
418 // nIndex is the requested frame - it is in range [0..nLen[
419 // create frame representation in VirtualDevices
425 // check buffering shortcuts, may already be created
426 aRetval
= tryTogetFromBuffer(nIndex
);
430 rVisitor
.append(aRetval
);
434 // if huge size (and not the buffered 1st frame) simply
436 if (mbHugeSize
&& 0 != nIndex
&& mnNextFrameToPrepare
<= nIndex
)
438 nIndex
= mnNextFrameToPrepare
% nLen
;
441 // frame not (yet) buffered or no buffering allowed, create it
442 const_cast<AnimatedGraphicPrimitive2D
*>(this)->createFrame(nIndex
);
444 // try to get from buffer again, may have been added from createFrame
445 aRetval
= tryTogetFromBuffer(nIndex
);
449 rVisitor
.append(aRetval
);
453 // did not work (not buffered and not 1st frame), create from buffer
454 aRetval
= createFromBuffer();
456 rVisitor
.append(aRetval
);
460 } // end of namespace primitive2d
461 } // end of namespace drawinglayer
463 namespace drawinglayer
465 namespace primitive2d
467 void create2DDecompositionOfGraphic(
468 Primitive2DContainer
& rContainer
,
469 const Graphic
& rGraphic
,
470 const basegfx::B2DHomMatrix
& rTransform
)
472 Primitive2DContainer aRetval
;
474 switch(rGraphic
.GetType())
476 case GraphicType::Bitmap
:
478 if(rGraphic
.IsAnimated())
480 // prepare specialized AnimatedGraphicPrimitive2D
482 aRetval
[0] = new AnimatedGraphicPrimitive2D(
486 else if(rGraphic
.getVectorGraphicData().get())
488 // embedded Vector Graphic Data fill, create embed transform
489 const basegfx::B2DRange
& rSvgRange(rGraphic
.getVectorGraphicData()->getRange());
491 if(basegfx::fTools::more(rSvgRange
.getWidth(), 0.0) && basegfx::fTools::more(rSvgRange
.getHeight(), 0.0))
493 // translate back to origin, scale to unit coordinates
494 basegfx::B2DHomMatrix
aEmbedVectorGraphic(
495 basegfx::utils::createTranslateB2DHomMatrix(
496 -rSvgRange
.getMinX(),
497 -rSvgRange
.getMinY()));
499 aEmbedVectorGraphic
.scale(
500 1.0 / rSvgRange
.getWidth(),
501 1.0 / rSvgRange
.getHeight());
503 // apply created object transformation
504 aEmbedVectorGraphic
= rTransform
* aEmbedVectorGraphic
;
506 // add Vector Graphic Data primitives embedded
508 aRetval
[0] = new TransformPrimitive2D(
510 rGraphic
.getVectorGraphicData()->getPrimitive2DSequence());
516 aRetval
[0] = new BitmapPrimitive2D(
517 rGraphic
.GetBitmapEx(),
524 case GraphicType::GdiMetafile
:
526 // create MetafilePrimitive2D
527 const GDIMetaFile
& rMetafile
= rGraphic
.GetGDIMetaFile();
530 aRetval
[0] = new MetafilePrimitive2D(
534 // #i100357# find out if clipping is needed for this primitive. Unfortunately,
535 // there exist Metafiles who's content is bigger than the proposed PrefSize set
536 // at them. This is an error, but we need to work around this
537 const Size
aMetaFilePrefSize(rMetafile
.GetPrefSize());
538 const Size
aMetaFileRealSize(
539 rMetafile
.GetBoundRect(
540 *Application::GetDefaultDevice()).GetSize());
542 if(aMetaFileRealSize
.getWidth() > aMetaFilePrefSize
.getWidth()
543 || aMetaFileRealSize
.getHeight() > aMetaFilePrefSize
.getHeight())
545 // clipping needed. Embed to MaskPrimitive2D. Create children and mask polygon
546 basegfx::B2DPolygon
aMaskPolygon(basegfx::utils::createUnitPolygon());
547 aMaskPolygon
.transform(rTransform
);
549 Primitive2DReference mask
= new MaskPrimitive2D(
550 basegfx::B2DPolyPolygon(aMaskPolygon
),
564 rContainer
.insert(rContainer
.end(), aRetval
.begin(), aRetval
.end());
567 Primitive2DContainer
create2DColorModifierEmbeddingsAsNeeded(
568 const Primitive2DContainer
& rChildren
,
569 GraphicDrawMode aGraphicDrawMode
,
578 Primitive2DContainer aRetval
;
580 if(rChildren
.empty())
582 // no child content, done
586 // set child content as retval; that is what will be used as child content in all
587 // embeddings from here
590 if(GraphicDrawMode::Watermark
== aGraphicDrawMode
)
592 // this is solved by applying fixed values additionally to luminance
593 // and contrast, do it here and reset DrawMode to GraphicDrawMode::Standard
594 // original in svtools uses:
595 // #define WATERMARK_LUM_OFFSET 50
596 // #define WATERMARK_CON_OFFSET -70
597 fLuminance
= std::clamp(fLuminance
+ 0.5, -1.0, 1.0);
598 fContrast
= std::clamp(fContrast
- 0.7, -1.0, 1.0);
599 aGraphicDrawMode
= GraphicDrawMode::Standard
;
602 // DrawMode (GraphicDrawMode::Watermark already handled)
603 switch(aGraphicDrawMode
)
605 case GraphicDrawMode::Greys
:
608 const Primitive2DReference
aPrimitiveGrey(
609 new ModifiedColorPrimitive2D(
611 basegfx::BColorModifierSharedPtr(
612 new basegfx::BColorModifier_gray())));
614 aRetval
= Primitive2DContainer
{ aPrimitiveGrey
};
617 case GraphicDrawMode::Mono
:
619 // convert to mono (black/white with threshold 0.5)
620 const Primitive2DReference
aPrimitiveBlackAndWhite(
621 new ModifiedColorPrimitive2D(
623 basegfx::BColorModifierSharedPtr(
624 new basegfx::BColorModifier_black_and_white(0.5))));
626 aRetval
= Primitive2DContainer
{ aPrimitiveBlackAndWhite
};
629 default: // case GraphicDrawMode::Standard:
632 aGraphicDrawMode
!= GraphicDrawMode::Watermark
633 && "OOps, GraphicDrawMode::Watermark should already be handled (see above)");
639 // mnContPercent, mnLumPercent, mnRPercent, mnGPercent, mnBPercent
640 // handled in a single call
641 if(!basegfx::fTools::equalZero(fLuminance
)
642 || !basegfx::fTools::equalZero(fContrast
)
643 || !basegfx::fTools::equalZero(fRed
)
644 || !basegfx::fTools::equalZero(fGreen
)
645 || !basegfx::fTools::equalZero(fBlue
))
647 const Primitive2DReference
aPrimitiveRGBLuminannceContrast(
648 new ModifiedColorPrimitive2D(
650 basegfx::BColorModifierSharedPtr(
651 new basegfx::BColorModifier_RGBLuminanceContrast(
658 aRetval
= Primitive2DContainer
{ aPrimitiveRGBLuminannceContrast
};
662 if(!basegfx::fTools::equal(fGamma
, 1.0))
664 const Primitive2DReference
aPrimitiveGamma(
665 new ModifiedColorPrimitive2D(
667 basegfx::BColorModifierSharedPtr(
668 new basegfx::BColorModifier_gamma(
671 aRetval
= Primitive2DContainer
{ aPrimitiveGamma
};
677 const Primitive2DReference
aPrimitiveInvert(
678 new ModifiedColorPrimitive2D(
680 basegfx::BColorModifierSharedPtr(
681 new basegfx::BColorModifier_invert())));
683 aRetval
= Primitive2DContainer
{ aPrimitiveInvert
};
689 } // end of namespace primitive2d
690 } // end of namespace drawinglayer
692 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */