Version 6.4.0.3, tag libreoffice-6.4.0.3
[LibreOffice.git] / drawinglayer / source / primitive2d / graphicprimitivehelper2d.cxx
blob62ea12db8178859a47e6f3959ffd1b701ceba4a5
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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 <sal/config.h>
22 #include <algorithm>
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
47 namespace primitive2d
49 class AnimatedGraphicPrimitive2D : public AnimatedSwitchPrimitive2D
51 private:
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
81 bool mbHugeSize;
83 /// helper methods
84 bool isValidData() const
86 return (GraphicType::Bitmap == maGraphic.GetType()
87 && maGraphic.IsAnimated()
88 && maAnimation.Count());
91 void ensureVirtualDeviceSizeAndState()
93 if (isValidData())
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
122 // is selected.
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.
132 if (0 == nWaitTime)
134 nWaitTime = 100;
137 return nWaitTime;
140 void createAndSetAnimationTiming()
142 if (isValidData())
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()));
168 #else
169 const Bitmap aMaskBitmap(maVirtualDeviceMask->GetBitmap(Point(), maVirtualDeviceMask->GetOutputSizePixel()));
170 #endif
172 return Primitive2DReference(
173 new BitmapPrimitive2D(
174 BitmapEx(aMainBitmap, aMaskBitmap),
175 getTransform()));
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)
194 if (!a.is())
196 bBufferingComplete = false;
197 break;
201 if (bBufferingComplete)
203 maVirtualDevice.disposeAndClear();
204 maVirtualDeviceMask.disposeAndClear();
209 else
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.
224 if (isValidData())
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)
235 // prepare step
236 const AnimationBitmap& rAnimationBitmap = maAnimation.Get(sal_uInt16(mnNextFrameToPrepare));
238 switch (rAnimationBitmap.meDisposal)
240 case Disposal::Not:
242 maVirtualDevice->DrawBitmapEx(rAnimationBitmap.maPositionPixel, rAnimationBitmap.maBitmapEx);
243 Bitmap aMask = rAnimationBitmap.maBitmapEx.GetMask();
245 if (aMask.IsEmpty())
247 const Point aEmpty;
248 const ::tools::Rectangle aRect(aEmpty, maVirtualDeviceMask->GetOutputSizePixel());
249 const Wallpaper aWallpaper(COL_BLACK);
250 maVirtualDeviceMask->DrawWallpaper(aRect, aWallpaper);
252 else
254 BitmapEx aExpandVisibilityMask(aMask, aMask);
255 maVirtualDeviceMask->DrawBitmapEx(rAnimationBitmap.maPositionPixel, aExpandVisibilityMask);
258 break;
260 case Disposal::Back:
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);
269 if (aMask.IsEmpty())
271 const ::tools::Rectangle aRect(rAnimationBitmap.maPositionPixel, aContent.GetSizePixel());
272 maVirtualDeviceMask->SetFillColor(COL_BLACK);
273 maVirtualDeviceMask->SetLineColor();
274 maVirtualDeviceMask->DrawRect(aRect);
276 else
278 BitmapEx aExpandVisibilityMask(aMask, aMask);
279 maVirtualDeviceMask->DrawBitmapEx(rAnimationBitmap.maPositionPixel, aExpandVisibilityMask);
282 break;
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);
289 break;
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];
314 else
316 // always buffer first frame, it's sometimes requested out-of-order
317 if (0 == nIndex && maBufferedFirstFrame.is())
319 return maBufferedFirstFrame;
323 return Primitive2DReference();
326 public:
327 /// constructor
328 AnimatedGraphicPrimitive2D(
329 const Graphic& rGraphic,
330 const basegfx::B2DHomMatrix& rTransform);
332 /// data read access
333 const Graphic& getGraphic() const { return maGraphic; }
334 const basegfx::B2DHomMatrix& getTransform() const { return maTransform; }
336 /// compare operator
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(),
349 false),
350 maTransform(rTransform),
351 maGraphic(rGraphic),
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),
359 mbHugeSize(false)
361 // initialize AnimationTiming, needed to detect which frame is requested
362 // in get2DDecomposition
363 createAndSetAnimationTiming();
365 // check if we allow buffering
366 if (isValidData())
368 // allow buffering up to a size of:
369 // - 64 frames
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)
384 mbHugeSize = true;
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
411 if (isValidData())
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
420 if (nIndex >= nLen)
422 nIndex = nLen - 1;
425 // check buffering shortcuts, may already be created
426 aRetval = tryTogetFromBuffer(nIndex);
428 if (aRetval.is())
430 rVisitor.append(aRetval);
431 return;
434 // if huge size (and not the buffered 1st frame) simply
435 // create next frame
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);
447 if (aRetval.is())
449 rVisitor.append(aRetval);
450 return;
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
481 aRetval.resize(1);
482 aRetval[0] = new AnimatedGraphicPrimitive2D(
483 rGraphic,
484 rTransform);
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
507 aRetval.resize(1);
508 aRetval[0] = new TransformPrimitive2D(
509 aEmbedVectorGraphic,
510 rGraphic.getVectorGraphicData()->getPrimitive2DSequence());
513 else
515 aRetval.resize(1);
516 aRetval[0] = new BitmapPrimitive2D(
517 rGraphic.GetBitmapEx(),
518 rTransform);
521 break;
524 case GraphicType::GdiMetafile :
526 // create MetafilePrimitive2D
527 const GDIMetaFile& rMetafile = rGraphic.GetGDIMetaFile();
529 aRetval.resize(1);
530 aRetval[0] = new MetafilePrimitive2D(
531 rTransform,
532 rMetafile);
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),
551 aRetval);
552 aRetval[0] = mask;
554 break;
557 default:
559 // nothing to create
560 break;
564 rContainer.insert(rContainer.end(), aRetval.begin(), aRetval.end());
567 Primitive2DContainer create2DColorModifierEmbeddingsAsNeeded(
568 const Primitive2DContainer& rChildren,
569 GraphicDrawMode aGraphicDrawMode,
570 double fLuminance,
571 double fContrast,
572 double fRed,
573 double fGreen,
574 double fBlue,
575 double fGamma,
576 bool bInvert)
578 Primitive2DContainer aRetval;
580 if(rChildren.empty())
582 // no child content, done
583 return aRetval;
586 // set child content as retval; that is what will be used as child content in all
587 // embeddings from here
588 aRetval = rChildren;
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:
607 // convert to grey
608 const Primitive2DReference aPrimitiveGrey(
609 new ModifiedColorPrimitive2D(
610 aRetval,
611 basegfx::BColorModifierSharedPtr(
612 new basegfx::BColorModifier_gray())));
614 aRetval = Primitive2DContainer { aPrimitiveGrey };
615 break;
617 case GraphicDrawMode::Mono:
619 // convert to mono (black/white with threshold 0.5)
620 const Primitive2DReference aPrimitiveBlackAndWhite(
621 new ModifiedColorPrimitive2D(
622 aRetval,
623 basegfx::BColorModifierSharedPtr(
624 new basegfx::BColorModifier_black_and_white(0.5))));
626 aRetval = Primitive2DContainer { aPrimitiveBlackAndWhite };
627 break;
629 default: // case GraphicDrawMode::Standard:
631 assert(
632 aGraphicDrawMode != GraphicDrawMode::Watermark
633 && "OOps, GraphicDrawMode::Watermark should already be handled (see above)");
634 // nothing to do
635 break;
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(
649 aRetval,
650 basegfx::BColorModifierSharedPtr(
651 new basegfx::BColorModifier_RGBLuminanceContrast(
652 fRed,
653 fGreen,
654 fBlue,
655 fLuminance,
656 fContrast))));
658 aRetval = Primitive2DContainer { aPrimitiveRGBLuminannceContrast };
661 // gamma (boolean)
662 if(!basegfx::fTools::equal(fGamma, 1.0))
664 const Primitive2DReference aPrimitiveGamma(
665 new ModifiedColorPrimitive2D(
666 aRetval,
667 basegfx::BColorModifierSharedPtr(
668 new basegfx::BColorModifier_gamma(
669 fGamma))));
671 aRetval = Primitive2DContainer { aPrimitiveGamma };
674 // invert (boolean)
675 if(bInvert)
677 const Primitive2DReference aPrimitiveInvert(
678 new ModifiedColorPrimitive2D(
679 aRetval,
680 basegfx::BColorModifierSharedPtr(
681 new basegfx::BColorModifier_invert())));
683 aRetval = Primitive2DContainer { aPrimitiveInvert };
686 return aRetval;
689 } // end of namespace primitive2d
690 } // end of namespace drawinglayer
692 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */