From a44a406de423b3d69183cc2e23f1e5f121518fab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Toma=C5=BE=20Vajngerl?= Date: Wed, 21 Aug 2024 11:25:05 +0200 Subject: [PATCH] Make rendering more flexible, render fields in separate layer If the amster page has a field (like slide number) then render the objects containing fields into a separate master slide layer. Also change the rendering in such a way to be more flexible and has less conditions. So in the renderer it doesn't really matter if we are rendering a master slide or slide, what matters only is if the object is already rendered and if we need to stop rendering for some specified reason (like we encountered a field and need to stop rendering and need to switch to a new layer). Change-Id: I37ea2528427bbc1b3de938f960fb344866ee9399 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/176911 Tested-by: Jenkins Reviewed-by: Miklos Vajna --- .../data/SlideRenderingTest_WithFields.odp | Bin 0 -> 26683 bytes sd/qa/unit/tiledrendering/tiledrendering.cxx | 116 +++++++++++- sd/source/ui/inc/SlideshowLayerRenderer.hxx | 102 ++++++++++- sd/source/ui/tools/SlideshowLayerRenderer.cxx | 172 +++++++++--------- 4 files changed, 295 insertions(+), 95 deletions(-) create mode 100644 sd/qa/unit/tiledrendering/data/SlideRenderingTest_WithFields.odp diff --git a/sd/qa/unit/tiledrendering/data/SlideRenderingTest_WithFields.odp b/sd/qa/unit/tiledrendering/data/SlideRenderingTest_WithFields.odp new file mode 100644 index 0000000000000000000000000000000000000000..960adc4c5c55bcd1c7c63771e8fed2d283d15c8e GIT binary patch literal 26683 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 } +CPPUNIT_TEST_FIXTURE(SdTiledRenderingTest, testSlideshowLayeredRendering_WithFields) +{ + // Check rendering of slideshow layers (as in the document): + // - master slide layer + // - main slide layer + + const bool bOutputPNG = false; // Control layer output to PNG files + + SdXImpressDocument* pXImpressDocument = createDoc("SlideRenderingTest_WithFields.odp"); + pXImpressDocument->initializeForTiledRendering(uno::Sequence()); + sd::ViewShell* pViewShell = pXImpressDocument->GetDocShell()->GetViewShell(); + CPPUNIT_ASSERT(pViewShell); + SdPage* pPage = pViewShell->GetActualPage(); + CPPUNIT_ASSERT(pPage); + sal_Int32 nViewWidth = 2000; + sal_Int32 nViewHeight = 2000; + CPPUNIT_ASSERT(pXImpressDocument->createSlideRenderer(0, nViewWidth, nViewHeight, true, true)); + CPPUNIT_ASSERT_EQUAL(2000, nViewWidth); + CPPUNIT_ASSERT_EQUAL(1125, nViewHeight); + + const Color aTransparentColor(ColorAlpha, 0x00000000); + { + std::vector pBuffer(nViewWidth * nViewHeight * 4); + bool bIsBitmapLayer = false; + OUString rJsonMsg; + CPPUNIT_ASSERT(!pXImpressDocument->renderNextSlideLayer(pBuffer.data(), bIsBitmapLayer, rJsonMsg)); + CPPUNIT_ASSERT(bIsBitmapLayer); + // TODO - check JSON content + // printf ("1 %s\n\n", rJsonMsg.toUtf8().getStr()); + + BitmapEx aBitmapEx = vcl::bitmap::CreateFromData(pBuffer.data(), nViewWidth, nViewHeight, nViewWidth * 4, /*nBitsPerPixel*/32, true, true); + if (bOutputPNG) + { + SvFileStream aStream("/home/quikee/XXX_01.png", StreamMode::WRITE | StreamMode::TRUNC); + vcl::PngImageWriter aPNGWriter(aStream); + aPNGWriter.write(aBitmapEx); + } + + // top-left corner + CPPUNIT_ASSERT_EQUAL(aTransparentColor, aBitmapEx.GetPixelColor(20, 20)); + + // bottom-left corner + CPPUNIT_ASSERT_EQUAL(aTransparentColor, aBitmapEx.GetPixelColor(20, nViewHeight - 20)); + + // bottom-right corner + CPPUNIT_ASSERT_EQUAL(Color(0xff, 0xd0, 0x40), aBitmapEx.GetPixelColor(nViewWidth - 20, nViewHeight - 20)); + } + + { + std::vector pBuffer(nViewWidth * nViewHeight * 4); + bool bIsBitmapLayer = false; + OUString rJsonMsg; + CPPUNIT_ASSERT(!pXImpressDocument->renderNextSlideLayer(pBuffer.data(), bIsBitmapLayer, rJsonMsg)); + CPPUNIT_ASSERT(bIsBitmapLayer); + // TODO - check JSON content + // printf ("2 %s\n\n", rJsonMsg.toUtf8().getStr()); + + BitmapEx aBitmapEx = vcl::bitmap::CreateFromData(pBuffer.data(), nViewWidth, nViewHeight, nViewWidth * 4, /*nBitsPerPixel*/32, true, true); + if (bOutputPNG) + { + SvFileStream aStream("/home/quikee/XXX_02.png", StreamMode::WRITE | StreamMode::TRUNC); + vcl::PngImageWriter aPNGWriter(aStream); + aPNGWriter.write(aBitmapEx); + } + + // top-left corner + CPPUNIT_ASSERT_EQUAL(aTransparentColor, aBitmapEx.GetPixelColor(20, 20)); + + // bottom-left corner + CPPUNIT_ASSERT_EQUAL(Color(0x90, 0x80, 0xff), aBitmapEx.GetPixelColor(20, nViewHeight - 20)); + + // bottom-right corner + CPPUNIT_ASSERT_EQUAL(aTransparentColor, aBitmapEx.GetPixelColor(nViewWidth - 20, nViewHeight - 20)); + } + + { + std::vector pBuffer(nViewWidth * nViewHeight * 4); + bool bIsBitmapLayer = false; + OUString rJsonMsg; + CPPUNIT_ASSERT(!pXImpressDocument->renderNextSlideLayer(pBuffer.data(), bIsBitmapLayer, rJsonMsg)); + CPPUNIT_ASSERT(bIsBitmapLayer); + // TODO - check JSON content + // printf ("3 %s\n\n", rJsonMsg.toUtf8().getStr()); + + BitmapEx aBitmapEx = vcl::bitmap::CreateFromData(pBuffer.data(), nViewWidth, nViewHeight, nViewWidth * 4, /*nBitsPerPixel*/32, true, true); + if (bOutputPNG) + { + SvFileStream aStream("/home/quikee/XXX_03.png", StreamMode::WRITE | StreamMode::TRUNC); + vcl::PngImageWriter aPNGWriter(aStream); + aPNGWriter.write(aBitmapEx); + } + + // top-left corner + CPPUNIT_ASSERT_EQUAL(Color(0x00, 0x50, 0x90), aBitmapEx.GetPixelColor(20, 20)); + + // bottom-left corner + CPPUNIT_ASSERT_EQUAL(aTransparentColor, aBitmapEx.GetPixelColor(20, nViewHeight - 20)); + + // bottom-right corner + CPPUNIT_ASSERT_EQUAL(aTransparentColor, aBitmapEx.GetPixelColor(nViewWidth - 20, nViewHeight - 20)); + } + + { + std::vector pBuffer(nViewWidth * nViewHeight * 4); + bool bIsBitmapLayer = false; + OUString rJsonMsg; + CPPUNIT_ASSERT(pXImpressDocument->renderNextSlideLayer(pBuffer.data(), bIsBitmapLayer, rJsonMsg)); + } + + pXImpressDocument->postSlideshowCleanup(); +} + CPPUNIT_PLUGIN_IMPLEMENT(); /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sd/source/ui/inc/SlideshowLayerRenderer.hxx b/sd/source/ui/inc/SlideshowLayerRenderer.hxx index 2f51c6e39b76..7f26d9b23b52 100644 --- a/sd/source/ui/inc/SlideshowLayerRenderer.hxx +++ b/sd/source/ui/inc/SlideshowLayerRenderer.hxx @@ -13,41 +13,125 @@ #include #include #include +#include class SdrPage; class SdrModel; +class SdrObject; class Size; namespace sd { struct RenderContext; -struct RenderOptions; -enum class SlideRenderStage +enum class RenderStage { Master, Slide }; +/** Holds rendering state, properties and switches through all rendering passes */ +struct RenderState +{ + RenderStage meStage = RenderStage::Master; + + sal_Int32 mnMasterIndex = 0; + bool mbStopRenderingWhenField = true; + + std::unordered_set maObjectsDone; + sal_Int32 mnIndex = 0; + + bool mbFirstObjectInPass = true; + bool mbPassHasOutput = false; + bool mbSkipAllInThisPass = false; + + sal_Int32 mnCurrentPass = 0; + + /// increments index depending on the current render stage + void incrementIndex() + { + if (meStage == RenderStage::Master) + mnMasterIndex++; + else + mnIndex++; + } + + /// returns the current stage as string + OString stageString() + { + if (meStage == RenderStage::Master) + return "MasterPage"_ostr; + return "DrawPage"_ostr; + } + + /// returns the current index depending on the current render stage + sal_Int32 currentIndex() + { + if (meStage == RenderStage::Master) + return mnMasterIndex; + return mnIndex; + } + + /// resets properties that are valid for one pass + void resetPass() + { + mbFirstObjectInPass = true; + mbPassHasOutput = false; + mbSkipAllInThisPass = false; + } + + /// return if there was no rendering output in the pass + bool noMoreOutput() + { + // no output and we don't skip anything + return !mbPassHasOutput && !mbSkipAllInThisPass; + } + + /// should include background in rendering + bool includeBackground() + { + // include background only if we are rendering the first pass + return mnCurrentPass == 0; + } + + bool isObjectAlreadyRendered(SdrObject* pObject) + { + return maObjectsDone.find(pObject) != maObjectsDone.end(); + } +}; + +/** Renders a slide */ class SD_DLLPUBLIC SlideshowLayerRenderer { +private: SdrPage& mrPage; SdrModel& mrModel; Size maSlideSize; + RenderState maRenderState; - std::deque maRenderStages; - - void cleanupRendering(RenderContext& rRenderContext); - void setupRendering(unsigned char* pBuffer, RenderContext& rRenderContext); - void createViewAndDraw(RenderContext& rRenderContext, RenderOptions const& rRenderOptions); + void createViewAndDraw(RenderContext& rRenderContext); + void writeJSON(OString& rJsonMsg); public: SlideshowLayerRenderer(SdrPage& rPage); + + /** Calculate and set the slide size depending on input desired size (in pixels) + * + * Input the desired size in pixels, and the actual size pixels will be caluclated + * depending on the size of the slide and the desired size. The size can differ, + * because the it must match the slide aspect ratio. + **/ Size calculateAndSetSizePixel(Size const& rDesiredSizePixel); + + /** Renders one layer + * + * The slide layer is rendered into the input buffer, which must be the byte size + * of the calcualted size in pixels * 4 (RGBA). + * The properties of the layer are written to the input string in JSON format. + * + * @returns false, if nothing was rendered and rendering is done */ bool render(unsigned char* pBuffer, OString& rJsonMsg); - bool renderMaster(unsigned char* pBuffer, OString& rJsonMsg); - bool renderSlide(unsigned char* pBuffer, OString& rJsonMsg); }; } // end of namespace sd diff --git a/sd/source/ui/tools/SlideshowLayerRenderer.cxx b/sd/source/ui/tools/SlideshowLayerRenderer.cxx index 486823b0aa9c..3e1297225d4c 100644 --- a/sd/source/ui/tools/SlideshowLayerRenderer.cxx +++ b/sd/source/ui/tools/SlideshowLayerRenderer.cxx @@ -24,13 +24,6 @@ namespace sd { -struct RenderOptions -{ - bool mbIncludeBackground = true; - bool mbSkipMainPageObjects = false; - bool mbSkipMasterPageObjects = false; -}; - struct RenderContext { SdrModel& mrModel; @@ -44,7 +37,7 @@ struct RenderContext , mrPage(rPage) , maVirtualDevice(DeviceFormat::WITHOUT_ALPHA) { - // Turn of spelling + // Turn off spelling SdrOutliner& rOutliner = mrModel.GetDrawOutliner(); mnSavedControlBits = rOutliner.GetControlWord(); rOutliner.SetControlWord(mnSavedControlBits & ~EEControlBits::ONLINESPELLING); @@ -77,14 +70,31 @@ struct RenderContext namespace { +bool hasFields(SdrObject* pObject) +{ + auto* pTextObject = dynamic_cast(pObject); + if (!pTextObject) + return false; + + OutlinerParaObject* pOutlinerParagraphObject = pTextObject->GetOutlinerParaObject(); + if (pOutlinerParagraphObject) + { + const EditTextObject& rEditText = pOutlinerParagraphObject->GetTextObject(); + if (rEditText.IsFieldObject()) + return true; + } + return false; +} + +/** VOC redirector to control which object should be rendered and which not */ class ObjectRedirector : public sdr::contact::ViewObjectContactRedirector { protected: - RenderOptions maOptions; + RenderState& mrRenderState; public: - ObjectRedirector(RenderOptions const& rOptions) - : maOptions(rOptions) + ObjectRedirector(RenderState& rRenderState) + : mrRenderState(rRenderState) { } @@ -93,36 +103,62 @@ public: const sdr::contact::DisplayInfo& rDisplayInfo, drawinglayer::primitive2d::Primitive2DDecompositionVisitor& rVisitor) override { + if (mrRenderState.mbSkipAllInThisPass) + return; + SdrObject* pObject = rOriginal.GetViewContact().TryToGetSdrObject(); - SdrPage* pPage = pObject ? pObject->getSdrPageFromSdrObject() : nullptr; + // Check if we are rendering an object that is valid to render (exists, and not empty) + if (pObject == nullptr || pObject->IsEmptyPresObj()) + return; - if (pObject == nullptr || pPage == nullptr) + SdrPage* pPage = pObject->getSdrPageFromSdrObject(); + // Does the object have a page + if (pPage == nullptr) + return; + + // is the object visible and not hidden by any option + const bool bVisible + = pObject->getSdrPageFromSdrObject()->checkVisibility(rOriginal, rDisplayInfo, true); + + if (!bVisible) + return; + + // Check if we have already rendered the object + if (mrRenderState.isObjectAlreadyRendered(pObject)) + return; + + // Check if we are in correct stage + if (mrRenderState.meStage == RenderStage::Master && !pPage->IsMasterPage()) { - // Not a SdrObject or a object not connected to a page (object with no page) + if (mrRenderState.mbFirstObjectInPass) + { + // if this is the first object - change from master to slide + // means we are done with rendering of master layers + mrRenderState.meStage = RenderStage::Slide; + } + else + { + // if not, we have to stop rendering all further objects + mrRenderState.mbSkipAllInThisPass = true; + return; + } + } + + if (mrRenderState.meStage == RenderStage::Master && hasFields(pObject) + && mrRenderState.mbStopRenderingWhenField && !mrRenderState.mbFirstObjectInPass) + { + mrRenderState.mbStopRenderingWhenField = false; + mrRenderState.mbSkipAllInThisPass = true; return; } - if (maOptions.mbSkipMasterPageObjects && pPage->IsMasterPage()) - return; - - if (maOptions.mbSkipMainPageObjects && !pPage->IsMasterPage()) - return; - - const bool bDoCreateGeometry( - pObject->getSdrPageFromSdrObject()->checkVisibility(rOriginal, rDisplayInfo, true)); - - if (!bDoCreateGeometry - && (pObject->GetObjInventor() != SdrInventor::Default - || pObject->GetObjIdentifier() != SdrObjKind::Page)) - { - return; - } - - if (pObject->IsEmptyPresObj()) - return; - + // render the object sdr::contact::ViewObjectContactRedirector::createRedirectedPrimitive2DSequence( rOriginal, rDisplayInfo, rVisitor); + + mrRenderState.mbFirstObjectInPass = false; + mrRenderState.maObjectsDone.insert(pObject); + mrRenderState.mbPassHasOutput = true; } }; @@ -148,8 +184,9 @@ SlideshowLayerRenderer::SlideshowLayerRenderer(SdrPage& rPage) , mrModel(rPage.getSdrModelFromSdrPage()) { if (!hasEmptyMaster(rPage)) - maRenderStages.emplace_back(SlideRenderStage::Master); - maRenderStages.emplace_back(SlideRenderStage::Slide); + maRenderState.meStage = RenderStage::Master; + else + maRenderState.meStage = RenderStage::Slide; } Size SlideshowLayerRenderer::calculateAndSetSizePixel(Size const& rDesiredSizePixel) @@ -161,8 +198,7 @@ Size SlideshowLayerRenderer::calculateAndSetSizePixel(Size const& rDesiredSizePi return maSlideSize; } -void SlideshowLayerRenderer::createViewAndDraw(RenderContext& rRenderContext, - RenderOptions const& rRenderOptions) +void SlideshowLayerRenderer::createViewAndDraw(RenderContext& rRenderContext) { SdrView aView(mrModel, rRenderContext.maVirtualDevice); aView.SetPageVisible(false); @@ -172,29 +208,24 @@ void SlideshowLayerRenderer::createViewAndDraw(RenderContext& rRenderContext, aView.SetGridVisible(false); aView.SetHlplVisible(false); aView.SetGlueVisible(false); - aView.setHideBackground(!rRenderOptions.mbIncludeBackground); + aView.setHideBackground(!maRenderState.includeBackground()); aView.ShowSdrPage(&mrPage); Size aPageSize(mrPage.GetSize()); Point aPoint; vcl::Region aRegion(::tools::Rectangle(aPoint, aPageSize)); - ObjectRedirector aRedirector(rRenderOptions); + ObjectRedirector aRedirector(maRenderState); aView.CompleteRedraw(rRenderContext.maVirtualDevice, aRegion, &aRedirector); } -bool SlideshowLayerRenderer::renderMaster(unsigned char* pBuffer, OString& rJsonMsg) +void SlideshowLayerRenderer::writeJSON(OString& rJsonMsg) { - RenderOptions aRenderOptions; - aRenderOptions.mbSkipMainPageObjects = true; - - RenderContext aRenderContext(pBuffer, mrModel, mrPage, maSlideSize); - createViewAndDraw(aRenderContext, aRenderOptions); - ::tools::JsonWriter aJsonWriter; - aJsonWriter.put("group", "MasterPage"); + aJsonWriter.put("group", maRenderState.stageString()); + aJsonWriter.put("index", maRenderState.currentIndex()); aJsonWriter.put("slideHash", GetInterfaceHash(GetXDrawPageForSdrPage(&mrPage))); - aJsonWriter.put("index", 0); + aJsonWriter.put("type", "bitmap"); { auto aContentNode = aJsonWriter.startNode("content"); @@ -203,47 +234,24 @@ bool SlideshowLayerRenderer::renderMaster(unsigned char* pBuffer, OString& rJson } rJsonMsg = aJsonWriter.finishAndGetAsOString(); - return true; -} - -bool SlideshowLayerRenderer::renderSlide(unsigned char* pBuffer, OString& rJsonMsg) -{ - RenderOptions aRenderOptions; - aRenderOptions.mbSkipMasterPageObjects = true; - - RenderContext aRenderContext(pBuffer, mrModel, mrPage, maSlideSize); - createViewAndDraw(aRenderContext, aRenderOptions); - - ::tools::JsonWriter aJsonWriter; - aJsonWriter.put("group", "DrawPage"); - aJsonWriter.put("slideHash", GetInterfaceHash(GetXDrawPageForSdrPage(&mrPage))); - aJsonWriter.put("index", 0); - aJsonWriter.put("type", "bitmap"); - { - auto aContentNode = aJsonWriter.startNode("content"); - aJsonWriter.put("type", "%IMAGETYPE%"); - aJsonWriter.put("checksum", "%IMAGECHECKSUM%"); - } - rJsonMsg = aJsonWriter.finishAndGetAsOString(); - - return true; + maRenderState.incrementIndex(); } bool SlideshowLayerRenderer::render(unsigned char* pBuffer, OString& rJsonMsg) { - if (maRenderStages.empty()) + // Reset state + maRenderState.resetPass(); + + RenderContext aRenderContext(pBuffer, mrModel, mrPage, maSlideSize); + createViewAndDraw(aRenderContext); + + // Check if we are done rendering all passes + if (maRenderState.noMoreOutput()) return false; - auto eRenderStage = maRenderStages.front(); - maRenderStages.pop_front(); + writeJSON(rJsonMsg); - switch (eRenderStage) - { - case SlideRenderStage::Master: - return renderMaster(pBuffer, rJsonMsg); - case SlideRenderStage::Slide: - return renderSlide(pBuffer, rJsonMsg); - }; + maRenderState.mnCurrentPass++; return true; }