sd/qa/unit/tiledrendering/data/SlideRenderingTest_WithFields.odp |binary sd/qa/unit/tiledrendering/tiledrendering.cxx | 116 +++++++ sd/source/ui/inc/SlideshowLayerRenderer.hxx | 102 ++++++ sd/source/ui/tools/SlideshowLayerRenderer.cxx | 154 +++++----- 4 files changed, 286 insertions(+), 86 deletions(-)
New commits: commit c68182b3fb8888aaf5932bc1e087aa13ee4a9a5e Author: Tomaž Vajngerl <tomaz.vajng...@collabora.co.uk> AuthorDate: Wed Aug 21 11:25:05 2024 +0200 Commit: Szymon Kłos <szymon.k...@collabora.com> CommitDate: Fri Sep 6 08:27:42 2024 +0200 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/+/172173 Tested-by: Jenkins CollaboraOffice <jenkinscollaboraoff...@gmail.com> Reviewed-by: Szymon Kłos <szymon.k...@collabora.com> 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 000000000000..960adc4c5c55 Binary files /dev/null and b/sd/qa/unit/tiledrendering/data/SlideRenderingTest_WithFields.odp differ diff --git a/sd/qa/unit/tiledrendering/tiledrendering.cxx b/sd/qa/unit/tiledrendering/tiledrendering.cxx index 604ec9047736..eab5a8f05669 100644 --- a/sd/qa/unit/tiledrendering/tiledrendering.cxx +++ b/sd/qa/unit/tiledrendering/tiledrendering.cxx @@ -3171,12 +3171,8 @@ CPPUNIT_TEST_FIXTURE(SdTiledRenderingTest, testPresentationInfo) CPPUNIT_TEST_FIXTURE(SdTiledRenderingTest, testSlideshowLayeredRendering) { // Check rendering of slideshow layers (as in the document): - // - background layer // - master slide layer // - main slide layer - // The doucment has nothing set for the background, so it should be application color = white - // On the master slide there is a (blue) rectangle on the right side - top-left should be transparent - // On the main slide there is a (green) rectanlge on the top-left size - right side should be transparent const bool bOutputPNG = false; // Control layer output to PNG files @@ -3256,6 +3252,118 @@ CPPUNIT_TEST_FIXTURE(SdTiledRenderingTest, testSlideshowLayeredRendering) pXImpressDocument->postSlideshowCleanup(); } +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<beans::PropertyValue>()); + 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<sal_uInt8> 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 ", 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<sal_uInt8> 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 ", 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<sal_uInt8> 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 ", 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<sal_uInt8> 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 <tools/gen.hxx> #include <rtl/string.hxx> #include <deque> +#include <unordered_set> 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<SdrObject*> 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<SlideRenderStage> 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 c6ad73a21e27..a9bc564bf1ea 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<SdrTextObj*>(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 { - SdrObject* pObject = rOriginal.GetViewContact().TryToGetSdrObject(); - SdrPage* pPage = pObject ? pObject->getSdrPageFromSdrObject() : nullptr; + if (mrRenderState.mbSkipAllInThisPass) + return; - if (pObject == nullptr || pPage == nullptr) - { - // Not a SdrObject or a object not connected to a page (object with no page) + SdrObject* pObject = rOriginal.GetViewContact().TryToGetSdrObject(); + // Check if we are rendering an object that is valid to render (exists, and not empty) + if (pObject == nullptr || pObject->IsEmptyPresObj()) return; - } - if (maOptions.mbSkipMasterPageObjects && pPage->IsMasterPage()) + SdrPage* pPage = pObject->getSdrPageFromSdrObject(); + // Does the object have a page + if (pPage == nullptr) return; - if (maOptions.mbSkipMainPageObjects && !pPage->IsMasterPage()) + // is the object visible and not hidden by any option + const bool bVisible + = pObject->getSdrPageFromSdrObject()->checkVisibility(rOriginal, rDisplayInfo, true); + + if (!bVisible) return; - const bool bDoCreateGeometry( - pObject->getSdrPageFromSdrObject()->checkVisibility(rOriginal, rDisplayInfo, true)); + // Check if we have already rendered the object + if (mrRenderState.isObjectAlreadyRendered(pObject)) + return; - if (!bDoCreateGeometry - && (pObject->GetObjInventor() != SdrInventor::Default - || pObject->GetObjIdentifier() != SdrObjKind::Page)) + // Check if we are in correct stage + if (mrRenderState.meStage == RenderStage::Master && !pPage->IsMasterPage()) { - return; + 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 (pObject->IsEmptyPresObj()) + if (mrRenderState.meStage == RenderStage::Master && hasFields(pObject) + && mrRenderState.mbStopRenderingWhenField && !mrRenderState.mbFirstObjectInPass) + { + mrRenderState.mbStopRenderingWhenField = false; + mrRenderState.mbSkipAllInThisPass = true; 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"); { ::tools::ScopedJsonWriterNode aContentNode = aJsonWriter.startNode("content"); @@ -203,47 +234,24 @@ bool SlideshowLayerRenderer::renderMaster(unsigned char* pBuffer, OString& rJson } rJsonMsg = aJsonWriter.finishAndGetAsOString(); - return true; + maRenderState.incrementIndex(); } -bool SlideshowLayerRenderer::renderSlide(unsigned char* pBuffer, OString& rJsonMsg) +bool SlideshowLayerRenderer::render(unsigned char* pBuffer, OString& rJsonMsg) { - RenderOptions aRenderOptions; - aRenderOptions.mbSkipMasterPageObjects = true; + // Reset state + maRenderState.resetPass(); RenderContext aRenderContext(pBuffer, mrModel, mrPage, maSlideSize); - createViewAndDraw(aRenderContext, aRenderOptions); + createViewAndDraw(aRenderContext); - ::tools::JsonWriter aJsonWriter; - aJsonWriter.put("group", "DrawPage"); - aJsonWriter.put("slideHash", GetInterfaceHash(GetXDrawPageForSdrPage(&mrPage))); - aJsonWriter.put("index", 0); - aJsonWriter.put("type", "bitmap"); - { - ::tools::ScopedJsonWriterNode aContentNode = aJsonWriter.startNode("content"); - aJsonWriter.put("type", "%IMAGETYPE%"); - aJsonWriter.put("checksum", "%IMAGECHECKSUM%"); - } - rJsonMsg = aJsonWriter.finishAndGetAsOString(); - - return true; -} - -bool SlideshowLayerRenderer::render(unsigned char* pBuffer, OString& rJsonMsg) -{ - if (maRenderStages.empty()) + // 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; }