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 <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
40 #include <vcl/animate/Animation.hxx>
41 #include <vcl/graph.hxx>
42 #include <vcl/virdev.hxx>
43 #include <vcl/svapp.hxx>
44 #include <toolkit/helper/vclunohelper.hxx>
45 #include <vcl/skia/SkiaHelper.hxx>
47 namespace drawinglayer::primitive2d
51 class AnimatedGraphicPrimitive2D
: public AnimatedSwitchPrimitive2D
54 /// the geometric definition
55 basegfx::B2DHomMatrix maTransform
;
57 /** the Graphic with all its content possibilities, here only
58 animated is allowed and gets checked by isValidData().
59 an instance of Graphic is used here since it's ref-counted
60 and thus a safe copy for now
62 const Graphic maGraphic
;
64 /// local animation processing data, excerpt from maGraphic
65 ::Animation maAnimation
;
67 /// the on-demand created VirtualDevices for frame creation
68 ScopedVclPtrInstance
< VirtualDevice
> maVirtualDevice
;
69 ScopedVclPtrInstance
< VirtualDevice
> maVirtualDeviceMask
;
71 // index of the next frame that would be regularly prepared
72 sal_uInt32 mnNextFrameToPrepare
;
74 /// buffering of 1st frame (always active)
75 Primitive2DReference maBufferedFirstFrame
;
77 /// buffering of all frames
78 std::vector
<Primitive2DReference
> maBufferedPrimitives
;
79 bool mbBufferingAllowed
;
81 /// set if the animation is huge so that just always the next frame
82 /// is used instead of using timing
86 bool isValidData() const
88 return (GraphicType::Bitmap
== maGraphic
.GetType()
89 && maGraphic
.IsAnimated()
90 && maAnimation
.Count());
93 void ensureVirtualDeviceSizeAndState()
98 const Size
aCurrent(maVirtualDevice
->GetOutputSizePixel());
99 const Size
aTarget(maAnimation
.GetDisplaySizePixel());
101 if (aCurrent
!= aTarget
)
103 maVirtualDevice
->EnableMapMode(false);
104 maVirtualDeviceMask
->EnableMapMode(false);
105 maVirtualDevice
->SetOutputSizePixel(aTarget
);
106 maVirtualDeviceMask
->SetOutputSizePixel(aTarget
);
109 maVirtualDevice
->Erase();
110 maVirtualDeviceMask
->Erase();
111 const ::tools::Rectangle
aRect(Point(0, 0), aTarget
);
112 maVirtualDeviceMask
->SetFillColor(COL_BLACK
);
113 maVirtualDeviceMask
->SetLineColor();
114 maVirtualDeviceMask
->DrawRect(aRect
);
117 sal_uInt32
generateStepTime(sal_uInt32 nIndex
) const
119 const AnimationFrame
& rAnimationFrame
= maAnimation
.Get(sal_uInt16(nIndex
));
120 sal_uInt32
nWaitTime(rAnimationFrame
.mnWait
* 10);
122 // Take care of special value for MultiPage TIFFs. ATM these shall just
123 // show their first page. Later we will offer some switching when object
125 if (ANIMATION_TIMEOUT_ON_CLICK
== rAnimationFrame
.mnWait
)
127 // ATM the huge value would block the timer, so
128 // use a long time to show first page (whole day)
129 nWaitTime
= 100 * 60 * 60 * 24;
132 // Bad trap: There are animated gifs with no set WaitTime (!).
133 // In that case use a default value.
142 void createAndSetAnimationTiming()
147 animation::AnimationEntryLoop
aAnimationLoop(maAnimation
.GetLoopCount() ? maAnimation
.GetLoopCount() : 0xffff);
148 const sal_uInt32
nCount(maAnimation
.Count());
150 for (sal_uInt32
a(0); a
< nCount
; a
++)
152 const sal_uInt32
aStepTime(generateStepTime(a
));
153 const animation::AnimationEntryFixed
aTime(static_cast<double>(aStepTime
), static_cast<double>(a
) / static_cast<double>(nCount
));
155 aAnimationLoop
.append(aTime
);
158 animation::AnimationEntryList aAnimationEntryList
;
159 aAnimationEntryList
.append(aAnimationLoop
);
161 setAnimationEntry(aAnimationEntryList
);
164 Primitive2DReference
createFromBuffer() const
166 // create BitmapEx by extracting from VirtualDevices
167 const Bitmap
aMainBitmap(maVirtualDevice
->GetBitmap(Point(), maVirtualDevice
->GetOutputSizePixel()));
168 bool useAlphaMask
= false;
169 #if defined(MACOSX) || defined(IOS)
172 // GetBitmap()-> AlphaMask is optimized with SkiaSalBitmap::InterpretAs8Bit(), 1bpp mask is not.
173 if( SkiaHelper::isVCLSkiaEnabled())
179 const AlphaMask
aMaskBitmap(maVirtualDeviceMask
->GetBitmap(Point(), maVirtualDeviceMask
->GetOutputSizePixel()));
180 bitmap
= BitmapEx(aMainBitmap
, aMaskBitmap
);
184 const Bitmap
aMaskBitmap(maVirtualDeviceMask
->GetBitmap(Point(), maVirtualDeviceMask
->GetOutputSizePixel()));
185 bitmap
= BitmapEx(aMainBitmap
, aMaskBitmap
);
188 return Primitive2DReference(
189 new BitmapPrimitive2D(
194 void checkSafeToBuffer(sal_uInt32 nIndex
)
196 if (mbBufferingAllowed
)
198 // all frames buffered
199 if (!maBufferedPrimitives
.empty() && nIndex
< maBufferedPrimitives
.size())
201 if (!maBufferedPrimitives
[nIndex
].is())
203 maBufferedPrimitives
[nIndex
] = createFromBuffer();
205 // check if buffering is complete
206 bool bBufferingComplete(true);
208 for (auto const & a
: maBufferedPrimitives
)
212 bBufferingComplete
= false;
217 if (bBufferingComplete
)
219 maVirtualDevice
.disposeAndClear();
220 maVirtualDeviceMask
.disposeAndClear();
227 // always buffer first frame
228 if (0 == nIndex
&& !maBufferedFirstFrame
.is())
230 maBufferedFirstFrame
= createFromBuffer();
235 void createFrame(sal_uInt32 nTarget
)
237 // mnNextFrameToPrepare is the target frame to create next (which implies that
238 // mnNextFrameToPrepare-1 *is* currently in the VirtualDevice when
239 // 0 != mnNextFrameToPrepare. nTarget is the target frame.
243 if (mnNextFrameToPrepare
> nTarget
)
245 // we are ahead request, reset mechanism to start at frame zero
246 ensureVirtualDeviceSizeAndState();
247 mnNextFrameToPrepare
= 0;
250 while (mnNextFrameToPrepare
<= nTarget
)
253 const AnimationFrame
& rAnimationFrame
= maAnimation
.Get(sal_uInt16(mnNextFrameToPrepare
));
255 switch (rAnimationFrame
.meDisposal
)
259 maVirtualDevice
->DrawBitmapEx(rAnimationFrame
.maPositionPixel
, rAnimationFrame
.maBitmapEx
);
260 Bitmap aAlphaMask
= rAnimationFrame
.maBitmapEx
.GetAlpha();
262 if (aAlphaMask
.IsEmpty())
265 const ::tools::Rectangle
aRect(aEmpty
, maVirtualDeviceMask
->GetOutputSizePixel());
266 const Wallpaper
aWallpaper(COL_BLACK
);
267 maVirtualDeviceMask
->DrawWallpaper(aRect
, aWallpaper
);
271 BitmapEx
aExpandVisibilityMask(aAlphaMask
, aAlphaMask
);
272 maVirtualDeviceMask
->DrawBitmapEx(rAnimationFrame
.maPositionPixel
, aExpandVisibilityMask
);
279 // #i70772# react on no mask, for primitives, too.
280 const Bitmap
& rMask(rAnimationFrame
.maBitmapEx
.GetAlpha());
281 const Bitmap
& rContent(rAnimationFrame
.maBitmapEx
.GetBitmap());
283 maVirtualDeviceMask
->Erase();
284 maVirtualDevice
->DrawBitmap(rAnimationFrame
.maPositionPixel
, rContent
);
288 const ::tools::Rectangle
aRect(rAnimationFrame
.maPositionPixel
, rContent
.GetSizePixel());
289 maVirtualDeviceMask
->SetFillColor(COL_BLACK
);
290 maVirtualDeviceMask
->SetLineColor();
291 maVirtualDeviceMask
->DrawRect(aRect
);
295 BitmapEx
aExpandVisibilityMask(rMask
, rMask
);
296 maVirtualDeviceMask
->DrawBitmapEx(rAnimationFrame
.maPositionPixel
, aExpandVisibilityMask
);
301 case Disposal::Previous
:
303 maVirtualDevice
->DrawBitmapEx(rAnimationFrame
.maPositionPixel
, rAnimationFrame
.maBitmapEx
);
304 BitmapEx
aExpandVisibilityMask(rAnimationFrame
.maBitmapEx
.GetAlpha(), rAnimationFrame
.maBitmapEx
.GetAlpha());
305 maVirtualDeviceMask
->DrawBitmapEx(rAnimationFrame
.maPositionPixel
, aExpandVisibilityMask
);
310 // to not waste created data, check adding to buffers
311 checkSafeToBuffer(mnNextFrameToPrepare
);
313 mnNextFrameToPrepare
++;
317 Primitive2DReference
tryTogetFromBuffer(sal_uInt32 nIndex
) const
319 if (mbBufferingAllowed
)
321 // all frames buffered, check if available
322 if (!maBufferedPrimitives
.empty() && nIndex
< maBufferedPrimitives
.size())
324 if (maBufferedPrimitives
[nIndex
].is())
326 return maBufferedPrimitives
[nIndex
];
332 // always buffer first frame, it's sometimes requested out-of-order
333 if (0 == nIndex
&& maBufferedFirstFrame
.is())
335 return maBufferedFirstFrame
;
339 return Primitive2DReference();
344 AnimatedGraphicPrimitive2D(
345 const Graphic
& rGraphic
,
346 basegfx::B2DHomMatrix aTransform
);
349 const basegfx::B2DHomMatrix
& getTransform() const { return maTransform
; }
352 virtual bool operator==(const BasePrimitive2D
& rPrimitive
) const override
;
354 /// override to deliver the correct expected frame dependent of timing
355 virtual void get2DDecomposition(Primitive2DDecompositionVisitor
& rVisitor
, const geometry::ViewInformation2D
& rViewInformation
) const override
;
360 AnimatedGraphicPrimitive2D::AnimatedGraphicPrimitive2D(
361 const Graphic
& rGraphic
,
362 basegfx::B2DHomMatrix aTransform
)
363 : AnimatedSwitchPrimitive2D(
364 animation::AnimationEntryList(),
365 Primitive2DContainer(),
367 maTransform(std::move(aTransform
)),
369 maAnimation(rGraphic
.GetAnimation()),
370 maVirtualDevice(*Application::GetDefaultDevice()),
371 maVirtualDeviceMask(*Application::GetDefaultDevice()),
372 mnNextFrameToPrepare(SAL_MAX_UINT32
),
373 mbBufferingAllowed(false),
376 // initialize AnimationTiming, needed to detect which frame is requested
377 // in get2DDecomposition
378 createAndSetAnimationTiming();
380 // check if we allow buffering
383 // allow buffering up to a size of:
385 // - sizes of 256x256 pixels
386 // This may be offered in option values if needed
387 static const sal_uInt64
nAllowedSize(64 * 256 * 256);
388 static const sal_uInt64
nHugeSize(10000000);
389 const Size
aTarget(maAnimation
.GetDisplaySizePixel());
390 const sal_uInt64
nUsedSize(static_cast<sal_uInt64
>(maAnimation
.Count()) * aTarget
.Width() * aTarget
.Height());
392 if (nUsedSize
< nAllowedSize
)
394 mbBufferingAllowed
= true;
397 if (nUsedSize
> nHugeSize
)
403 // prepare buffer space
404 if (mbBufferingAllowed
&& isValidData())
406 maBufferedPrimitives
.resize(maAnimation
.Count());
410 bool AnimatedGraphicPrimitive2D::operator==(const BasePrimitive2D
& rPrimitive
) const
412 // do not use 'GroupPrimitive2D::operator==' here, that would compare
413 // the children. Also do not use 'BasePrimitive2D::operator==', that would
414 // check the ID-Type. Since we are a simple derivation without own ID,
415 // use the dynamic_cast RTTI directly
416 const AnimatedGraphicPrimitive2D
* pCompare
= dynamic_cast<const AnimatedGraphicPrimitive2D
*>(&rPrimitive
);
418 // use operator== of Graphic - if that is equal, the basic definition is equal
419 return (nullptr != pCompare
420 && getTransform() == pCompare
->getTransform()
421 && maGraphic
== pCompare
->maGraphic
);
424 void AnimatedGraphicPrimitive2D::get2DDecomposition(Primitive2DDecompositionVisitor
& rVisitor
, const geometry::ViewInformation2D
& rViewInformation
) const
429 Primitive2DReference aRetval
;
430 const double fState(getAnimationEntry().getStateAtTime(rViewInformation
.getViewTime()));
431 const sal_uInt32
nLen(maAnimation
.Count());
432 sal_uInt32
nIndex(basegfx::fround(fState
* static_cast<double>(nLen
)));
434 // nIndex is the requested frame - it is in range [0..nLen[
435 // create frame representation in VirtualDevices
441 // check buffering shortcuts, may already be created
442 aRetval
= tryTogetFromBuffer(nIndex
);
446 rVisitor
.visit(aRetval
);
450 // if huge size (and not the buffered 1st frame) simply
452 if (mbHugeSize
&& 0 != nIndex
&& mnNextFrameToPrepare
<= nIndex
)
454 nIndex
= mnNextFrameToPrepare
% nLen
;
457 // frame not (yet) buffered or no buffering allowed, create it
458 const_cast<AnimatedGraphicPrimitive2D
*>(this)->createFrame(nIndex
);
460 // try to get from buffer again, may have been added from createFrame
461 aRetval
= tryTogetFromBuffer(nIndex
);
465 rVisitor
.visit(aRetval
);
469 // did not work (not buffered and not 1st frame), create from buffer
470 aRetval
= createFromBuffer();
472 rVisitor
.visit(aRetval
);
475 } // end of namespace
477 namespace drawinglayer::primitive2d
479 void create2DDecompositionOfGraphic(
480 Primitive2DContainer
& rContainer
,
481 const Graphic
& rGraphic
,
482 const basegfx::B2DHomMatrix
& rTransform
)
484 Primitive2DContainer aRetval
;
486 switch(rGraphic
.GetType())
488 case GraphicType::Bitmap
:
490 if(rGraphic
.IsAnimated())
492 // prepare specialized AnimatedGraphicPrimitive2D
494 aRetval
[0] = new AnimatedGraphicPrimitive2D(
498 else if(rGraphic
.getVectorGraphicData())
500 // embedded Vector Graphic Data fill, create embed transform
501 const basegfx::B2DRange
& rSvgRange(rGraphic
.getVectorGraphicData()->getRange());
503 if(basegfx::fTools::more(rSvgRange
.getWidth(), 0.0) && basegfx::fTools::more(rSvgRange
.getHeight(), 0.0))
505 // translate back to origin, scale to unit coordinates
506 basegfx::B2DHomMatrix
aEmbedVectorGraphic(
507 basegfx::utils::createTranslateB2DHomMatrix(
508 -rSvgRange
.getMinX(),
509 -rSvgRange
.getMinY()));
511 aEmbedVectorGraphic
.scale(
512 1.0 / rSvgRange
.getWidth(),
513 1.0 / rSvgRange
.getHeight());
515 // apply created object transformation
516 aEmbedVectorGraphic
= rTransform
* aEmbedVectorGraphic
;
518 // add Vector Graphic Data primitives embedded
520 aRetval
[0] = new TransformPrimitive2D(
522 Primitive2DContainer(rGraphic
.getVectorGraphicData()->getPrimitive2DSequence()));
528 aRetval
[0] = new BitmapPrimitive2D(
529 rGraphic
.GetBitmapEx(),
536 case GraphicType::GdiMetafile
:
538 // create MetafilePrimitive2D
539 const GDIMetaFile
& rMetafile
= rGraphic
.GetGDIMetaFile();
542 aRetval
[0] = new MetafilePrimitive2D(
546 // #i100357# find out if clipping is needed for this primitive. Unfortunately,
547 // there exist Metafiles who's content is bigger than the proposed PrefSize set
548 // at them. This is an error, but we need to work around this
549 const Size
aMetaFilePrefSize(rMetafile
.GetPrefSize());
550 const Size
aMetaFileRealSize(
551 rMetafile
.GetBoundRect(
552 *Application::GetDefaultDevice()).GetSize());
554 if(aMetaFileRealSize
.getWidth() > aMetaFilePrefSize
.getWidth()
555 || aMetaFileRealSize
.getHeight() > aMetaFilePrefSize
.getHeight())
557 // clipping needed. Embed to MaskPrimitive2D. Create children and mask polygon
558 basegfx::B2DPolygon
aMaskPolygon(basegfx::utils::createUnitPolygon());
559 aMaskPolygon
.transform(rTransform
);
561 Primitive2DReference mask
= new MaskPrimitive2D(
562 basegfx::B2DPolyPolygon(aMaskPolygon
),
564 aRetval
= Primitive2DContainer
{ mask
};
576 rContainer
.append(std::move(aRetval
));
579 Primitive2DContainer
create2DColorModifierEmbeddingsAsNeeded(
580 Primitive2DContainer
&& rChildren
,
581 GraphicDrawMode aGraphicDrawMode
,
590 Primitive2DContainer aRetval
;
592 if(rChildren
.empty())
594 // no child content, done
598 // set child content as retval; that is what will be used as child content in all
599 // embeddings from here
600 aRetval
= std::move(rChildren
);
602 if(GraphicDrawMode::Watermark
== aGraphicDrawMode
)
604 // this is solved by applying fixed values additionally to luminance
605 // and contrast, do it here and reset DrawMode to GraphicDrawMode::Standard
606 // original in svtools uses:
607 // #define WATERMARK_LUM_OFFSET 50
608 // #define WATERMARK_CON_OFFSET -70
609 fLuminance
= std::clamp(fLuminance
+ 0.5, -1.0, 1.0);
610 fContrast
= std::clamp(fContrast
- 0.7, -1.0, 1.0);
611 aGraphicDrawMode
= GraphicDrawMode::Standard
;
614 // DrawMode (GraphicDrawMode::Watermark already handled)
615 switch(aGraphicDrawMode
)
617 case GraphicDrawMode::Greys
:
620 const Primitive2DReference
aPrimitiveGrey(
621 new ModifiedColorPrimitive2D(
623 std::make_shared
<basegfx::BColorModifier_gray
>()));
625 aRetval
= Primitive2DContainer
{ aPrimitiveGrey
};
628 case GraphicDrawMode::Mono
:
630 // convert to mono (black/white with threshold 0.5)
631 const Primitive2DReference
aPrimitiveBlackAndWhite(
632 new ModifiedColorPrimitive2D(
634 std::make_shared
<basegfx::BColorModifier_black_and_white
>(0.5)));
636 aRetval
= Primitive2DContainer
{ aPrimitiveBlackAndWhite
};
639 default: // case GraphicDrawMode::Standard:
642 aGraphicDrawMode
!= GraphicDrawMode::Watermark
643 && "OOps, GraphicDrawMode::Watermark should already be handled (see above)");
649 // mnContPercent, mnLumPercent, mnRPercent, mnGPercent, mnBPercent
650 // handled in a single call
651 if(!basegfx::fTools::equalZero(fLuminance
)
652 || !basegfx::fTools::equalZero(fContrast
)
653 || !basegfx::fTools::equalZero(fRed
)
654 || !basegfx::fTools::equalZero(fGreen
)
655 || !basegfx::fTools::equalZero(fBlue
))
657 const Primitive2DReference
aPrimitiveRGBLuminannceContrast(
658 new ModifiedColorPrimitive2D(
660 std::make_shared
<basegfx::BColorModifier_RGBLuminanceContrast
>(
667 aRetval
= Primitive2DContainer
{ aPrimitiveRGBLuminannceContrast
};
671 if(!basegfx::fTools::equal(fGamma
, 1.0))
673 const Primitive2DReference
aPrimitiveGamma(
674 new ModifiedColorPrimitive2D(
676 std::make_shared
<basegfx::BColorModifier_gamma
>(
679 aRetval
= Primitive2DContainer
{ aPrimitiveGamma
};
685 const Primitive2DReference
aPrimitiveInvert(
686 new ModifiedColorPrimitive2D(
688 std::make_shared
<basegfx::BColorModifier_invert
>()));
690 aRetval
= Primitive2DContainer
{ aPrimitiveInvert
};
696 } // end of namespace
698 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */