render master and slide content separately (drawinglayer render)

This renders master and the main slide as 2 separate layers, which
is useful because in a huge slideshow we can reuse the master slide
and only render the rest of the slide, which should be more compact
in size as the master slide is the one that usually contains the
(complex) background.

Change-Id: I5e86d718b7ab3b03bd0b6146ce4df218a4dd72d4
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/176622
Tested-by: Jenkins
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
This commit is contained in:
Miklos Vajna
2024-11-15 13:20:06 +01:00
parent 2fbb7e0472
commit 981ce9b3a9
8 changed files with 219 additions and 49 deletions

View File

@@ -207,6 +207,7 @@ protected:
bool mbHideChart : 1;
bool mbHideDraw : 1; // hide draw objects other than form controls
bool mbHideFormControl : 1; // hide form controls only
bool mbHideBackground : 1; // don't draw the (page's or matser page's) background
bool mbPaintTextEdit : 1; // if should paint currently edited text
public:
@@ -473,10 +474,13 @@ public:
bool getHideChart() const { return mbHideChart; }
bool getHideDraw() const { return mbHideDraw; }
bool getHideFormControl() const { return mbHideFormControl; }
bool getHideBackground() const { return mbHideBackground; }
void setHideOle(bool bNew) { if(bNew != mbHideOle) mbHideOle = bNew; }
void setHideChart(bool bNew) { if(bNew != mbHideChart) mbHideChart = bNew; }
void setHideDraw(bool bNew) { if(bNew != mbHideDraw) mbHideDraw = bNew; }
void setHideFormControl(bool bNew) { if(bNew != mbHideFormControl) mbHideFormControl = bNew; }
void setHideBackground(bool bNew) { mbHideBackground = bNew; }
void SetGridCoarse(const Size& rSiz) { maGridBig=rSiz; }
void SetGridFine(const Size& rSiz) {

View File

@@ -3125,8 +3125,9 @@ CPPUNIT_TEST_FIXTURE(SdTiledRenderingTest, testSlideshowLayeredRendering)
// The document 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) rectangle on the top-left size - right side should be transparent
// enable layer output to PNG files
const bool bOutputPNG = false;
const bool bOutputPNG = false; // Control layer output to PNG files
SdXImpressDocument* pXImpressDocument = createDoc("SlideRenderingTest.odp");
pXImpressDocument->initializeForTiledRendering(uno::Sequence<beans::PropertyValue>());
sd::ViewShell* pViewShell = pXImpressDocument->GetDocShell()->GetViewShell();
@@ -3137,16 +3138,18 @@ CPPUNIT_TEST_FIXTURE(SdTiledRenderingTest, testSlideshowLayeredRendering)
sal_Int32 nViewHeight = 2000;
CPPUNIT_ASSERT(pXImpressDocument->createSlideRenderer(0, nViewWidth, nViewHeight, true, true));
CPPUNIT_ASSERT_EQUAL(2000, nViewWidth);
CPPUNIT_ASSERT_EQUAL(1125, nViewHeight); // adjusted to the slide aspect ratio
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\n\n", rJsonMsg.toUtf8().getStr());
{
BitmapEx aBitmapEx = vcl::bitmap::CreateFromData(pBuffer.data(), nViewWidth, nViewHeight, nViewWidth * 4, /*nBitsPerPixel*/32, true, true);
if (bOutputPNG)
{
@@ -3155,14 +3158,49 @@ CPPUNIT_TEST_FIXTURE(SdTiledRenderingTest, testSlideshowLayeredRendering)
aPNGWriter.write(aBitmapEx);
}
// top-left corner
CPPUNIT_ASSERT_EQUAL(aTransparentColor, aBitmapEx.GetPixelColor(20, 20));
// bottom-left corner
CPPUNIT_ASSERT_EQUAL(Color(ColorAlpha, 0x00000000), aBitmapEx.GetPixelColor(20, nViewHeight - 20));
CPPUNIT_ASSERT_EQUAL(aTransparentColor, aBitmapEx.GetPixelColor(20, nViewHeight - 20));
// bottom-right corner
CPPUNIT_ASSERT_EQUAL(Color(0xff, 0xd5, 0x46), aBitmapEx.GetPixelColor(nViewWidth - 20, nViewHeight - 20));
CPPUNIT_ASSERT_EQUAL(Color(0xff, 0xd0, 0x40), aBitmapEx.GetPixelColor(nViewWidth - 20, nViewHeight - 20));
}
// should return true - no more content
{
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\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(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();
}

View File

@@ -12,23 +12,36 @@
#include <sddllapi.h>
#include <tools/gen.hxx>
#include <rtl/string.hxx>
#include <deque>
class SdrPage;
class BitmapEx;
class SdrModel;
class Size;
namespace sd
{
enum class SlideRenderStage
{
Master,
Slide
};
class SD_DLLPUBLIC SlideshowLayerRenderer
{
SdrPage* mpPage;
SdrPage& mrPage;
SdrModel& mrModel;
Size maSlideSize;
bool bRenderDone = false;
std::deque<SlideRenderStage> maRenderStages;
public:
SlideshowLayerRenderer(SdrPage* pPage);
SlideshowLayerRenderer(SdrPage& rPage);
Size calculateAndSetSizePixel(Size const& rDesiredSizePixel);
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

View File

@@ -14,7 +14,9 @@
#include <svx/unoapi.hxx>
#include <svx/sdr/contact/viewobjectcontact.hxx>
#include <svx/sdr/contact/viewcontact.hxx>
#include <svx/sdr/contact/objectcontact.hxx>
#include <svx/svdoutl.hxx>
#include <svx/svdpagv.hxx>
#include <vcl/virdev.hxx>
#include <tools/helpers.hxx>
#include <tools/json_writer.hxx>
@@ -23,9 +25,23 @@ namespace sd
{
namespace
{
class SelectObjectRedirector : public sdr::contact::ViewObjectContactRedirector
struct RedirectorOptions
{
bool mbSkipMainPageObjects = false;
bool mbSkipMasterPageObjects = false;
};
class ObjectRedirector : public sdr::contact::ViewObjectContactRedirector
{
protected:
RedirectorOptions maOptions;
public:
ObjectRedirector(RedirectorOptions const& rOptions)
: maOptions(rOptions)
{
}
virtual void createRedirectedPrimitive2DSequence(
const sdr::contact::ViewObjectContact& rOriginal,
const sdr::contact::DisplayInfo& rDisplayInfo,
@@ -37,12 +53,15 @@ public:
if (pObject == nullptr || pPage == nullptr)
{
// Not a SdrObject or a object not connected to a page (object with no page)
//sdr::contact::ViewObjectContactRedirector::createRedirectedPrimitive2DSequence(
// rOriginal, rDisplayInfo, rVisitor);
return;
}
if (maOptions.mbSkipMasterPageObjects && pPage->IsMasterPage())
return;
if (maOptions.mbSkipMainPageObjects && !pPage->IsMasterPage())
return;
const bool bDoCreateGeometry(
pObject->getSdrPageFromSdrObject()->checkVisibility(rOriginal, rDisplayInfo, true));
@@ -60,43 +79,57 @@ public:
rOriginal, rDisplayInfo, rVisitor);
}
};
bool hasEmptyMaster(SdrPage const& rPage)
{
if (!rPage.TRG_HasMasterPage())
return true;
SdrPage& rMaster = rPage.TRG_GetMasterPage();
for (size_t i = 0; i < rMaster.GetObjCount(); i++)
{
auto pObject = rMaster.GetObj(i);
if (!pObject->IsEmptyPresObj())
return false;
}
return true;
}
SlideshowLayerRenderer::SlideshowLayerRenderer(SdrPage* pPage)
: mpPage(pPage)
} // end anonymous namespace
SlideshowLayerRenderer::SlideshowLayerRenderer(SdrPage& rPage)
: mrPage(rPage)
, mrModel(rPage.getSdrModelFromSdrPage())
{
if (!hasEmptyMaster(rPage))
maRenderStages.emplace_back(SlideRenderStage::Master);
maRenderStages.emplace_back(SlideRenderStage::Slide);
}
Size SlideshowLayerRenderer::calculateAndSetSizePixel(Size const& rDesiredSizePixel)
{
if (!mpPage)
return Size();
double fRatio = double(mpPage->GetHeight()) / mpPage->GetWidth();
double fRatio = double(mrPage.GetHeight()) / mrPage.GetWidth();
Size aSize(rDesiredSizePixel.Width(), ::tools::Long(rDesiredSizePixel.Width() * fRatio));
maSlideSize = aSize;
return maSlideSize;
}
bool SlideshowLayerRenderer::render(unsigned char* pBuffer, OString& rJsonMsg)
bool SlideshowLayerRenderer::renderMaster(unsigned char* pBuffer, OString& rJsonMsg)
{
if (bRenderDone)
return false;
if (!mpPage)
return false;
SdrModel& rModel = mpPage->getSdrModelFromSdrPage();
SdrOutliner& rOutliner = mrModel.GetDrawOutliner();
const EEControlBits nOldControlBits(rOutliner.GetControlWord());
EEControlBits nControlBits = nOldControlBits & ~EEControlBits::ONLINESPELLING;
rOutliner.SetControlWord(nControlBits);
ScopedVclPtrInstance<VirtualDevice> pDevice(DeviceFormat::WITHOUT_ALPHA);
pDevice->SetBackground(Wallpaper(COL_TRANSPARENT));
pDevice->SetOutputSizePixelScaleOffsetAndLOKBuffer(maSlideSize, Fraction(2.0), Point(),
pDevice->SetOutputSizePixelScaleOffsetAndLOKBuffer(maSlideSize, Fraction(1.0), Point(),
pBuffer);
Point aPoint;
Size aPageSize(mpPage->GetSize());
Size aPageSize(mrPage.GetSize());
MapMode aMapMode(MapUnit::Map100thMM);
const Fraction aFracX(maSlideSize.Width(), pDevice->LogicToPixel(aPageSize, aMapMode).Width());
@@ -108,7 +141,7 @@ bool SlideshowLayerRenderer::render(unsigned char* pBuffer, OString& rJsonMsg)
pDevice->SetMapMode(aMapMode);
SdrView aView(rModel, pDevice);
SdrView aView(mrModel, pDevice);
aView.SetPageVisible(false);
aView.SetPageShadowVisible(false);
@@ -117,16 +150,16 @@ bool SlideshowLayerRenderer::render(unsigned char* pBuffer, OString& rJsonMsg)
aView.SetGridVisible(false);
aView.SetHlplVisible(false);
aView.SetGlueVisible(false);
aView.ShowSdrPage(mpPage);
aView.setHideBackground(false);
aView.ShowSdrPage(&mrPage);
vcl::Region aRegion(::tools::Rectangle(aPoint, aPageSize));
SelectObjectRedirector aRedirector;
ObjectRedirector aRedirector({ .mbSkipMainPageObjects = true });
aView.CompleteRedraw(pDevice, aRegion, &aRedirector);
::tools::JsonWriter aJsonWriter;
aJsonWriter.put("group", "DrawPage");
aJsonWriter.put("slideHash", GetInterfaceHash(GetXDrawPageForSdrPage(mpPage)));
aJsonWriter.put("group", "MasterPage");
aJsonWriter.put("slideHash", GetInterfaceHash(GetXDrawPageForSdrPage(&mrPage)));
aJsonWriter.put("index", 0);
aJsonWriter.put("type", "bitmap");
{
@@ -136,7 +169,85 @@ bool SlideshowLayerRenderer::render(unsigned char* pBuffer, OString& rJsonMsg)
}
rJsonMsg = aJsonWriter.finishAndGetAsOString();
bRenderDone = true;
rOutliner.SetControlWord(nOldControlBits);
return true;
}
bool SlideshowLayerRenderer::renderSlide(unsigned char* pBuffer, OString& rJsonMsg)
{
SdrOutliner& rOutliner = mrModel.GetDrawOutliner();
const EEControlBits nOldControlBits(rOutliner.GetControlWord());
EEControlBits nControlBits = nOldControlBits & ~EEControlBits::ONLINESPELLING;
rOutliner.SetControlWord(nControlBits);
ScopedVclPtrInstance<VirtualDevice> pDevice(DeviceFormat::WITHOUT_ALPHA);
pDevice->SetBackground(Wallpaper(COL_TRANSPARENT));
pDevice->SetOutputSizePixelScaleOffsetAndLOKBuffer(maSlideSize, Fraction(1.0), Point(),
pBuffer);
Point aPoint;
Size aPageSize(mrPage.GetSize());
MapMode aMapMode(MapUnit::Map100thMM);
const Fraction aFracX(maSlideSize.Width(), pDevice->LogicToPixel(aPageSize, aMapMode).Width());
aMapMode.SetScaleX(aFracX);
const Fraction aFracY(maSlideSize.Height(),
pDevice->LogicToPixel(aPageSize, aMapMode).Height());
aMapMode.SetScaleY(aFracY);
pDevice->SetMapMode(aMapMode);
SdrView aView(mrModel, pDevice);
aView.SetPageVisible(false);
aView.SetPageShadowVisible(false);
aView.SetPageBorderVisible(false);
aView.SetBordVisible(false);
aView.SetGridVisible(false);
aView.SetHlplVisible(false);
aView.SetGlueVisible(false);
aView.setHideBackground(true);
aView.ShowSdrPage(&mrPage);
vcl::Region aRegion(::tools::Rectangle(aPoint, aPageSize));
ObjectRedirector aRedirector({ .mbSkipMasterPageObjects = true });
aView.CompleteRedraw(pDevice, aRegion, &aRedirector);
::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();
rOutliner.SetControlWord(nOldControlBits);
return true;
}
bool SlideshowLayerRenderer::render(unsigned char* pBuffer, OString& rJsonMsg)
{
if (maRenderStages.empty())
return false;
auto eRenderStage = maRenderStages.front();
maRenderStages.pop_front();
switch (eRenderStage)
{
case SlideRenderStage::Master:
return renderMaster(pBuffer, rJsonMsg);
case SlideRenderStage::Slide:
return renderSlide(pBuffer, rJsonMsg);
};
return true;
}

View File

@@ -4457,7 +4457,7 @@ bool SdXImpressDocument::createSlideRenderer(
if (!pPage)
return false;
mpSlideshowLayerRenderer.reset(new SlideshowLayerRenderer(pPage));
mpSlideshowLayerRenderer.reset(new SlideshowLayerRenderer(*pPage));
Size aDesiredSize(nViewWidth, nViewHeight);
Size aCalculatedSize = mpSlideshowLayerRenderer->calculateAndSetSizePixel(aDesiredSize);
nViewWidth = aCalculatedSize.Width();

View File

@@ -23,6 +23,8 @@
#include <svx/sdr/contact/displayinfo.hxx>
#include <svx/sdr/contact/objectcontact.hxx>
#include <svx/svdpage.hxx>
#include <svx/svdpagv.hxx>
#include <svx/svdview.hxx>
#include <drawinglayer/primitive2d/maskprimitive2d.hxx>
#include <basegfx/polygon/b2dpolygontools.hxx>
#include <basegfx/polygon/b2dpolygon.hxx>
@@ -78,12 +80,13 @@ namespace sdr::contact
aPreprocessedLayers &= rDescriptor.GetVisibleLayers();
rDisplayInfo.SetProcessLayers(aPreprocessedLayers);
rDisplayInfo.SetSubContentActive(true);
const SdrPageView* pSdrPageView = GetObjectContact().TryToGetSdrPageView();
bool bHideBackground = pSdrPageView ? pSdrPageView->GetView().getHideBackground() : false;
// check layer visibility (traditionally was member of layer 1)
if(aPreprocessedLayers.IsSet(SdrLayerID(1)))
{
// hide PageBackground for special DrawModes; historical reasons
if(!GetObjectContact().isDrawModeGray() && !GetObjectContact().isDrawModeHighContrast())
if (!bHideBackground && !GetObjectContact().isDrawModeGray() && !GetObjectContact().isDrawModeHighContrast())
{
// if visible, create the default background primitive sequence
static_cast< ViewContactOfMasterPageDescriptor& >(GetViewContact()).getViewIndependentPrimitive2DContainer(rVisitor);

View File

@@ -165,6 +165,7 @@ SdrPaintView::SdrPaintView(SdrModel& rSdrModel, OutputDevice* pOut)
, mbHideChart(false)
, mbHideDraw(false)
, mbHideFormControl(false)
, mbHideBackground(false)
, mbPaintTextEdit(true)
, maGridColor(COL_BLACK)
{