calc: on editing invalidation of view with different zoom is wrong
[LibreOffice.git] / drawinglayer / source / primitive2d / graphicprimitivehelper2d.cxx
blob344e7328a84f48ac28f9732f8d61d23b7b9ebc06
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 <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 <utility>
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
49 namespace {
51 class AnimatedGraphicPrimitive2D : public AnimatedSwitchPrimitive2D
53 private:
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
83 bool mbHugeSize;
85 /// helper methods
86 bool isValidData() const
88 return (GraphicType::Bitmap == maGraphic.GetType()
89 && maGraphic.IsAnimated()
90 && maAnimation.Count());
93 void ensureVirtualDeviceSizeAndState()
95 if (!isValidData())
96 return;
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
124 // is selected.
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.
134 if (0 == nWaitTime)
136 nWaitTime = 100;
139 return nWaitTime;
142 void createAndSetAnimationTiming()
144 if (!isValidData())
145 return;
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)
170 useAlphaMask = true;
171 #else
172 // GetBitmap()-> AlphaMask is optimized with SkiaSalBitmap::InterpretAs8Bit(), 1bpp mask is not.
173 if( SkiaHelper::isVCLSkiaEnabled())
174 useAlphaMask = true;
175 #endif
176 BitmapEx bitmap;
177 if( useAlphaMask )
179 const AlphaMask aMaskBitmap(maVirtualDeviceMask->GetBitmap(Point(), maVirtualDeviceMask->GetOutputSizePixel()));
180 bitmap = BitmapEx(aMainBitmap, aMaskBitmap);
182 else
184 const Bitmap aMaskBitmap(maVirtualDeviceMask->GetBitmap(Point(), maVirtualDeviceMask->GetOutputSizePixel()));
185 bitmap = BitmapEx(aMainBitmap, aMaskBitmap);
188 return Primitive2DReference(
189 new BitmapPrimitive2D(
190 bitmap,
191 getTransform()));
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)
210 if (!a.is())
212 bBufferingComplete = false;
213 break;
217 if (bBufferingComplete)
219 maVirtualDevice.disposeAndClear();
220 maVirtualDeviceMask.disposeAndClear();
225 else
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.
240 if (!isValidData())
241 return;
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)
252 // prepare step
253 const AnimationFrame& rAnimationFrame = maAnimation.Get(sal_uInt16(mnNextFrameToPrepare));
255 switch (rAnimationFrame.meDisposal)
257 case Disposal::Not:
259 maVirtualDevice->DrawBitmapEx(rAnimationFrame.maPositionPixel, rAnimationFrame.maBitmapEx);
260 Bitmap aAlphaMask = rAnimationFrame.maBitmapEx.GetAlpha();
262 if (aAlphaMask.IsEmpty())
264 const Point aEmpty;
265 const ::tools::Rectangle aRect(aEmpty, maVirtualDeviceMask->GetOutputSizePixel());
266 const Wallpaper aWallpaper(COL_BLACK);
267 maVirtualDeviceMask->DrawWallpaper(aRect, aWallpaper);
269 else
271 BitmapEx aExpandVisibilityMask(aAlphaMask, aAlphaMask);
272 maVirtualDeviceMask->DrawBitmapEx(rAnimationFrame.maPositionPixel, aExpandVisibilityMask);
275 break;
277 case Disposal::Back:
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);
286 if (rMask.IsEmpty())
288 const ::tools::Rectangle aRect(rAnimationFrame.maPositionPixel, rContent.GetSizePixel());
289 maVirtualDeviceMask->SetFillColor(COL_BLACK);
290 maVirtualDeviceMask->SetLineColor();
291 maVirtualDeviceMask->DrawRect(aRect);
293 else
295 BitmapEx aExpandVisibilityMask(rMask, rMask);
296 maVirtualDeviceMask->DrawBitmapEx(rAnimationFrame.maPositionPixel, aExpandVisibilityMask);
299 break;
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);
306 break;
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];
330 else
332 // always buffer first frame, it's sometimes requested out-of-order
333 if (0 == nIndex && maBufferedFirstFrame.is())
335 return maBufferedFirstFrame;
339 return Primitive2DReference();
342 public:
343 /// constructor
344 AnimatedGraphicPrimitive2D(
345 const Graphic& rGraphic,
346 basegfx::B2DHomMatrix aTransform);
348 /// data read access
349 const basegfx::B2DHomMatrix& getTransform() const { return maTransform; }
351 /// compare operator
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(),
366 false),
367 maTransform(std::move(aTransform)),
368 maGraphic(rGraphic),
369 maAnimation(rGraphic.GetAnimation()),
370 maVirtualDevice(*Application::GetDefaultDevice()),
371 maVirtualDeviceMask(*Application::GetDefaultDevice()),
372 mnNextFrameToPrepare(SAL_MAX_UINT32),
373 mbBufferingAllowed(false),
374 mbHugeSize(false)
376 // initialize AnimationTiming, needed to detect which frame is requested
377 // in get2DDecomposition
378 createAndSetAnimationTiming();
380 // check if we allow buffering
381 if (isValidData())
383 // allow buffering up to a size of:
384 // - 64 frames
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)
399 mbHugeSize = true;
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
426 if (!isValidData())
427 return;
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
436 if (nIndex >= nLen)
438 nIndex = nLen - 1;
441 // check buffering shortcuts, may already be created
442 aRetval = tryTogetFromBuffer(nIndex);
444 if (aRetval.is())
446 rVisitor.visit(aRetval);
447 return;
450 // if huge size (and not the buffered 1st frame) simply
451 // create next frame
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);
463 if (aRetval.is())
465 rVisitor.visit(aRetval);
466 return;
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
493 aRetval.resize(1);
494 aRetval[0] = new AnimatedGraphicPrimitive2D(
495 rGraphic,
496 rTransform);
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
519 aRetval.resize(1);
520 aRetval[0] = new TransformPrimitive2D(
521 aEmbedVectorGraphic,
522 Primitive2DContainer(rGraphic.getVectorGraphicData()->getPrimitive2DSequence()));
525 else
527 aRetval.resize(1);
528 aRetval[0] = new BitmapPrimitive2D(
529 rGraphic.GetBitmapEx(),
530 rTransform);
533 break;
536 case GraphicType::GdiMetafile :
538 // create MetafilePrimitive2D
539 const GDIMetaFile& rMetafile = rGraphic.GetGDIMetaFile();
541 aRetval.resize(1);
542 aRetval[0] = new MetafilePrimitive2D(
543 rTransform,
544 rMetafile);
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),
563 std::move(aRetval));
564 aRetval = Primitive2DContainer { mask };
566 break;
569 default:
571 // nothing to create
572 break;
576 rContainer.append(std::move(aRetval));
579 Primitive2DContainer create2DColorModifierEmbeddingsAsNeeded(
580 Primitive2DContainer&& rChildren,
581 GraphicDrawMode aGraphicDrawMode,
582 double fLuminance,
583 double fContrast,
584 double fRed,
585 double fGreen,
586 double fBlue,
587 double fGamma,
588 bool bInvert)
590 Primitive2DContainer aRetval;
592 if(rChildren.empty())
594 // no child content, done
595 return aRetval;
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:
619 // convert to grey
620 const Primitive2DReference aPrimitiveGrey(
621 new ModifiedColorPrimitive2D(
622 std::move(aRetval),
623 std::make_shared<basegfx::BColorModifier_gray>()));
625 aRetval = Primitive2DContainer { aPrimitiveGrey };
626 break;
628 case GraphicDrawMode::Mono:
630 // convert to mono (black/white with threshold 0.5)
631 const Primitive2DReference aPrimitiveBlackAndWhite(
632 new ModifiedColorPrimitive2D(
633 std::move(aRetval),
634 std::make_shared<basegfx::BColorModifier_black_and_white>(0.5)));
636 aRetval = Primitive2DContainer { aPrimitiveBlackAndWhite };
637 break;
639 default: // case GraphicDrawMode::Standard:
641 assert(
642 aGraphicDrawMode != GraphicDrawMode::Watermark
643 && "OOps, GraphicDrawMode::Watermark should already be handled (see above)");
644 // nothing to do
645 break;
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(
659 std::move(aRetval),
660 std::make_shared<basegfx::BColorModifier_RGBLuminanceContrast>(
661 fRed,
662 fGreen,
663 fBlue,
664 fLuminance,
665 fContrast)));
667 aRetval = Primitive2DContainer { aPrimitiveRGBLuminannceContrast };
670 // gamma (boolean)
671 if(!basegfx::fTools::equal(fGamma, 1.0))
673 const Primitive2DReference aPrimitiveGamma(
674 new ModifiedColorPrimitive2D(
675 std::move(aRetval),
676 std::make_shared<basegfx::BColorModifier_gamma>(
677 fGamma)));
679 aRetval = Primitive2DContainer { aPrimitiveGamma };
682 // invert (boolean)
683 if(bInvert)
685 const Primitive2DReference aPrimitiveInvert(
686 new ModifiedColorPrimitive2D(
687 std::move(aRetval),
688 std::make_shared<basegfx::BColorModifier_invert>()));
690 aRetval = Primitive2DContainer { aPrimitiveInvert };
693 return aRetval;
696 } // end of namespace
698 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */