1
0
Fork 0
libreoffice/oox/qa/unit/testscene3d.cxx
Daniel Baumann 8e63e14cf6
Adding upstream version 4:25.2.3.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
2025-06-22 16:20:04 +02:00

641 lines
32 KiB
C++

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* This file is part of the LibreOffice project.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
#include <test/unoapixml_test.hxx>
#include <com/sun/star/awt/Rectangle.hpp>
#include <com/sun/star/awt/Size.hpp>
#include <com/sun/star/beans/XPropertySet.hpp>
#include <com/sun/star/drawing/EnhancedCustomShapeParameterPair.hpp>
#include <com/sun/star/drawing/EnhancedCustomShapeParameterType.hpp>
#include <com/sun/star/drawing/Position3D.hpp>
#include <com/sun/star/drawing/ProjectionMode.hpp>
#include <com/sun/star/drawing/XDrawPagesSupplier.hpp>
#include <comphelper/sequenceashashmap.hxx>
#include <editeng/unoprnms.hxx>
#include <sfx2/viewsh.hxx>
#include <svx/svdobj.hxx>
#include <svx/svdograf.hxx>
#include <svx/svdview.hxx>
#include <tools/color.hxx>
#include <vcl/bitmapex.hxx>
using namespace css;
/// Covers tests for scene3d import and export, available since LO 24.8., see tdf#70039
class TestScene3d : public UnoApiXmlTest
{
public:
TestScene3d()
: UnoApiXmlTest(u"/oox/qa/unit/data/"_ustr)
{
}
protected:
// get shape with nShapeIndex from page nPageIndex
uno::Reference<drawing::XShape> getShape(sal_uInt8 nShapeIndex, sal_uInt8 nPageIndex);
// Converts the shape 0 on page 0 to a bitmap and returns this bitmap.
// It assumes, that shape 0 on page 0 is the only shape.
void getShapeAsBitmap(BitmapEx& rBMP, sal_uInt8 nShapeIndex);
};
uno::Reference<drawing::XShape> TestScene3d::getShape(sal_uInt8 nShapeIndex, sal_uInt8 nPageIndex)
{
uno::Reference<drawing::XDrawPagesSupplier> xDrawPagesSupplier(mxComponent,
uno::UNO_QUERY_THROW);
CPPUNIT_ASSERT_MESSAGE("Could not get XDrawPagesSupplier", xDrawPagesSupplier.is());
uno::Reference<drawing::XDrawPages> xDrawPages(xDrawPagesSupplier->getDrawPages());
uno::Reference<drawing::XDrawPage> xDrawPage(xDrawPages->getByIndex(nPageIndex),
uno::UNO_QUERY_THROW);
CPPUNIT_ASSERT_MESSAGE("Could not get xDrawPage", xDrawPage.is());
uno::Reference<drawing::XShape> xShape(xDrawPage->getByIndex(nShapeIndex), uno::UNO_QUERY);
CPPUNIT_ASSERT_MESSAGE("Could not get xShape", xShape.is());
return xShape;
}
void TestScene3d::getShapeAsBitmap(BitmapEx& rBMP, sal_uInt8 nShapeIndex)
{
SfxViewShell* pViewShell = SfxViewShell::Current();
CPPUNIT_ASSERT(pViewShell);
SdrView* pSdrView = pViewShell->GetDrawView();
// Mark object and convert it to bitmap
uno::Reference<drawing::XShape> xShape3D(getShape(nShapeIndex, 0));
SdrObject* pSdrShape(SdrObject::getSdrObjectFromXShape(xShape3D));
pSdrView->MarkObj(pSdrShape, pSdrView->GetSdrPageView());
dispatchCommand(mxComponent, u".uno:ConvertIntoBitmap"_ustr, {});
pSdrView->UnmarkAll();
// Get graphic
uno::Reference<drawing::XShape> xShapeBmp(getShape(nShapeIndex, 0));
SdrGrafObj* pGrafObj = dynamic_cast<SdrGrafObj*>(SdrObject::getSdrObjectFromXShape(xShapeBmp));
CPPUNIT_ASSERT_MESSAGE("No image object created", pGrafObj);
const Graphic& rGraphic = pGrafObj->GetGraphic();
rBMP = rGraphic.GetBitmapEx();
CPPUNIT_ASSERT_MESSAGE("No bitmap", !rBMP.IsEmpty());
}
namespace
{
void lcl_AssertColorsApproximateEqual(const ::Color& aExpected, const ::Color& aActual)
{
// Currently (March 2024), the import of lighting and material is only approximately possible.
// Thus colors are not identical. When the import will be improved, the tolerances should be
// reduced. The test uses HSB instead of RGB, because differences in hue are more irritating and
// should be detected as a priority. That is not possible with GetColorError() method.
sal_uInt16 nExpH;
sal_uInt16 nExpS;
sal_uInt16 nExpB;
sal_uInt16 nActH;
sal_uInt16 nActS;
sal_uInt16 nActB;
aExpected.RGBtoHSB(nExpH, nExpS, nExpB);
aActual.RGBtoHSB(nActH, nActS, nActB);
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Hue", nExpH, nActH, 2);
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Saturation", nExpS, nActS, 13);
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE("Brightness", nExpB, nActB, 11);
}
} // end anonymous namespace
CPPUNIT_TEST_FIXTURE(TestScene3d, test_isometricRightUp)
{
// Given a document with a scene3d element on a shape. Without the fix, the shape was imported as
// flat 2D shape without extrusion. This test covers some basic properties.
loadFromFile(u"Scene3d_isometricRightUp.pptx");
uno::Reference<drawing::XShape> xShape(getShape(1, 0)); // shape 1 on page 0
// Prepare property maps
uno::Reference<beans::XPropertySet> xShapeProps(xShape, uno::UNO_QUERY);
uno::Sequence<beans::PropertyValue> aGeoPropSeq;
xShapeProps->getPropertyValue(u"CustomShapeGeometry"_ustr) >>= aGeoPropSeq;
comphelper::SequenceAsHashMap aGeoPropMap(aGeoPropSeq);
uno::Sequence<beans::PropertyValue> aExtrusionSeq;
aGeoPropMap.getValue(u"Extrusion"_ustr) >>= aExtrusionSeq;
comphelper::SequenceAsHashMap aExtrusionPropMap(aExtrusionSeq);
// Make sure extrusion is on.
bool bIsExtruded(false);
aExtrusionPropMap.getValue(u"Extrusion"_ustr) >>= bIsExtruded;
CPPUNIT_ASSERT_MESSAGE("error: Extrusion not enabled", bIsExtruded);
// Make sure the extrusion properties correspond to the camera preset type.
// projection mode should be PARALLEL
drawing::ProjectionMode eProjectionMode = drawing::ProjectionMode_PERSPECTIVE;
aExtrusionPropMap.getValue(u"ProjectionMode"_ustr) >>= eProjectionMode;
CPPUNIT_ASSERT_EQUAL(drawing::ProjectionMode_PARALLEL, eProjectionMode);
// For Skew, Origin, RotateAngle and Depth
drawing::EnhancedCustomShapeParameterPair aParaPair;
// Should be front projection, which means Skew amount and angle are zero.
aExtrusionPropMap.getValue(u"Skew"_ustr) >>= aParaPair;
CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, aParaPair.First.Value.get<double>(), 1E-14);
CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, aParaPair.Second.Value.get<double>(), 1E-14);
// .. and Origin x and y are zero.
aExtrusionPropMap.getValue(u"Origin"_ustr) >>= aParaPair;
CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, aParaPair.First.Value.get<double>(), 1E-14);
CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, aParaPair.Second.Value.get<double>(), 1E-14);
// Rotation around x-axis should be -35deg, around y-axis should be -45deg.
aExtrusionPropMap.getValue(UNO_NAME_MISC_OBJ_ROTATEANGLE) >>= aParaPair;
CPPUNIT_ASSERT_DOUBLES_EQUAL(-35.0, aParaPair.First.Value.get<double>(), 1E-14);
CPPUNIT_ASSERT_DOUBLES_EQUAL(-45.0, aParaPair.Second.Value.get<double>(), 1E-14);
// Depth should be 36pt = 1270Hmm without shift
aExtrusionPropMap.getValue(u"Depth"_ustr) >>= aParaPair;
CPPUNIT_ASSERT_DOUBLES_EQUAL(1270.0, aParaPair.First.Value.get<double>(), 1E-14);
CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, aParaPair.Second.Value.get<double>(), 1E-14);
// This shape does not use an extrusion color, but we enable it in all cases and set
// its color, because MSO and LO have different defaults.
bool bIsExtrusionColorEnabled(false);
aExtrusionPropMap.getValue(u"Color"_ustr) >>= bIsExtrusionColorEnabled;
CPPUNIT_ASSERT_MESSAGE("error: Extrusion color not enabled", bIsExtrusionColorEnabled);
}
CPPUNIT_TEST_FIXTURE(TestScene3d, test_legacyObliqueBottomRight)
{
// The legacy preset camera types correspond to the geometry available in the UI of LibreOffice.
// They are not available in the UI of MS Office, but if given, user can change some properties.
// The test includes some of them.
loadFromFile(u"Scene3d_legacyObliqueBottomRight.pptx");
uno::Reference<drawing::XShape> xShape(getShape(1, 0)); // shape 1 on page 0
// Prepare property maps
uno::Reference<beans::XPropertySet> xShapeProps(xShape, uno::UNO_QUERY);
uno::Sequence<beans::PropertyValue> aGeoPropSeq;
xShapeProps->getPropertyValue(u"CustomShapeGeometry"_ustr) >>= aGeoPropSeq;
comphelper::SequenceAsHashMap aGeoPropMap(aGeoPropSeq);
uno::Sequence<beans::PropertyValue> aExtrusionSeq;
aGeoPropMap.getValue(u"Extrusion"_ustr) >>= aExtrusionSeq;
comphelper::SequenceAsHashMap aExtrusionPropMap(aExtrusionSeq);
// For Skew, Origin, RotateAngle and Depth
drawing::EnhancedCustomShapeParameterPair aParaPair;
// Should be a slanted projection with shifted origin
aExtrusionPropMap.getValue(u"Skew"_ustr) >>= aParaPair;
CPPUNIT_ASSERT_DOUBLES_EQUAL(50.0, aParaPair.First.Value.get<double>(), 1E-14);
CPPUNIT_ASSERT_DOUBLES_EQUAL(135.0, aParaPair.Second.Value.get<double>(), 1E-14);
aExtrusionPropMap.getValue(u"Origin"_ustr) >>= aParaPair;
CPPUNIT_ASSERT_DOUBLES_EQUAL(0.5, aParaPair.First.Value.get<double>(), 1E-14);
CPPUNIT_ASSERT_DOUBLES_EQUAL(0.5, aParaPair.Second.Value.get<double>(), 1E-14);
// The shape has a rotation. Rotations on x-axis and y-axis belong to the extrusion rotation,
// rotation on z-axis belongs to the shape rotation.
aExtrusionPropMap.getValue(UNO_NAME_MISC_OBJ_ROTATEANGLE) >>= aParaPair;
CPPUNIT_ASSERT_DOUBLES_EQUAL(-3.45117839701884, aParaPair.First.Value.get<double>(), 1E-12);
CPPUNIT_ASSERT_DOUBLES_EQUAL(9.3912858020435, aParaPair.Second.Value.get<double>(), 1E-12);
sal_Int32 nZRotate; // unit 1/100 degree
xShapeProps->getPropertyValue(UNO_NAME_MISC_OBJ_ROTATEANGLE) >>= nZRotate;
CPPUNIT_ASSERT_EQUAL(sal_Int32(2028), nZRotate);
// Depth should be 36pt = 1270Hmm. The second value corresponds to the 'Distance from ground'
// setting in the UI of MS Office, only that it is relative to the depth in ODF.
aExtrusionPropMap.getValue(u"Depth"_ustr) >>= aParaPair;
CPPUNIT_ASSERT_DOUBLES_EQUAL(1270.0, aParaPair.First.Value.get<double>(), 1E-14);
CPPUNIT_ASSERT_DOUBLES_EQUAL(0.5, aParaPair.Second.Value.get<double>(), 1E-14);
// This shape uses an extrusion color. The extrusion color itself does not belong to the
// extrusion but to the shape fill properties. The color is not a complex color but still RGB.
bool bIsExtrusionColorEnabled(false);
aExtrusionPropMap.getValue(u"Color"_ustr) >>= bIsExtrusionColorEnabled;
CPPUNIT_ASSERT_MESSAGE("error: Extrusion color is not enabled", bIsExtrusionColorEnabled);
Color nColor;
xShapeProps->getPropertyValue(UNO_NAME_FILLCOLOR_2) >>= nColor;
CPPUNIT_ASSERT_EQUAL(Color(0x89c064), nColor);
}
CPPUNIT_TEST_FIXTURE(TestScene3d, test_obliqueTopRight)
{
// MS Office applies the shape rotation after the camera rotations in case of oblique preset
// camera types. That needs to be converted to the 'first shape rotation' specification of ODF.
// That conversion results in angles not available in the UI of LibreOffice, but LO can
// render them. The shape has got these rotation in the UI of MS Office: 60° shape ,
// 50° on x-axis, 10° on y-axis and 40° on z-axis.
loadFromFile(u"Scene3d_obliqueTopRight.pptx");
uno::Reference<drawing::XShape> xShape(getShape(0, 0)); // shape 0 on page 0
// Prepare property maps
uno::Reference<beans::XPropertySet> xShapeProps(xShape, uno::UNO_QUERY);
uno::Sequence<beans::PropertyValue> aGeoPropSeq;
xShapeProps->getPropertyValue(u"CustomShapeGeometry"_ustr) >>= aGeoPropSeq;
comphelper::SequenceAsHashMap aGeoPropMap(aGeoPropSeq);
uno::Sequence<beans::PropertyValue> aExtrusionSeq;
aGeoPropMap.getValue(u"Extrusion"_ustr) >>= aExtrusionSeq;
comphelper::SequenceAsHashMap aExtrusionPropMap(aExtrusionSeq);
// For Skew and RotateAngle
drawing::EnhancedCustomShapeParameterPair aParaPair;
// Should be an oblique projection with non default skew values
aExtrusionPropMap.getValue(u"Skew"_ustr) >>= aParaPair;
CPPUNIT_ASSERT_DOUBLES_EQUAL(30.0, aParaPair.First.Value.get<double>(), 1E-14);
// -135° from preset camera type, minus 60° shape rotation
CPPUNIT_ASSERT_DOUBLES_EQUAL(-195.0, aParaPair.Second.Value.get<double>(), 1E-14);
// Because of the needed conversions the resulting angles are very different from
// those set in the UI of MS Office.
aExtrusionPropMap.getValue(UNO_NAME_MISC_OBJ_ROTATEANGLE) >>= aParaPair;
CPPUNIT_ASSERT_DOUBLES_EQUAL(-45.7369327638437, aParaPair.First.Value.get<double>(), 1E-12);
CPPUNIT_ASSERT_DOUBLES_EQUAL(24.9102537477636, aParaPair.Second.Value.get<double>(), 1E-12);
sal_Int32 nZRotate; // unit 1/100 degree
xShapeProps->getPropertyValue(UNO_NAME_MISC_OBJ_ROTATEANGLE) >>= nZRotate;
CPPUNIT_ASSERT_EQUAL(sal_Int32(34597), nZRotate);
// The test uses BoundRect to verify position and size of the resulting scene.
// The tolerance 10 is estimated and can be adjusted if required for HiDPI.
awt::Rectangle aBoundRect;
xShapeProps->getPropertyValue(UNO_NAME_MISC_OBJ_BOUNDRECT) >>= aBoundRect;
CPPUNIT_ASSERT_DOUBLES_EQUAL(8325, aBoundRect.Width, 10);
CPPUNIT_ASSERT_DOUBLES_EQUAL(7804, aBoundRect.Height, 10);
CPPUNIT_ASSERT_DOUBLES_EQUAL(8272, aBoundRect.X, 10);
CPPUNIT_ASSERT_DOUBLES_EQUAL(3510, aBoundRect.Y, 10);
}
CPPUNIT_TEST_FIXTURE(TestScene3d, test_orthographicFront)
{
// MS Office users might use the z-rotation instead of the shape rotation without intending an
// extrusion. As of March 2024 we import the shape as 2D shape in such case, because extrusion
// looses the stroke, see tdf#159334.
loadFromFile(u"Scene3d_orthographicFront.pptx");
uno::Reference<drawing::XShape> xShape(getShape(0, 0)); // shape 0 on page 0
// Make sure that in case extrusion properties exist, the extrusion is disabled.
uno::Reference<beans::XPropertySet> xShapeProps(xShape, uno::UNO_QUERY);
uno::Sequence<beans::PropertyValue> aGeoPropSeq;
xShapeProps->getPropertyValue(u"CustomShapeGeometry"_ustr) >>= aGeoPropSeq;
comphelper::SequenceAsHashMap aGeoPropMap(aGeoPropSeq);
uno::Sequence<beans::PropertyValue> aExtrusionSeq;
if (aGeoPropMap.getValue(u"Extrusion"_ustr) >>= aExtrusionSeq)
{
comphelper::SequenceAsHashMap aExtrusionPropMap(aExtrusionSeq);
bool bIsExtruded(true);
aExtrusionPropMap.getValue(u"Extrusion"_ustr) >>= bIsExtruded;
CPPUNIT_ASSERT_MESSAGE("error: Extrusion is enabled", !bIsExtruded);
}
// Make sure the shape is nevertheless rotated.
sal_Int32 nZRotate; // unit 1/100 degree
xShapeProps->getPropertyValue(UNO_NAME_MISC_OBJ_ROTATEANGLE) >>= nZRotate;
CPPUNIT_ASSERT_EQUAL(sal_Int32(9000), nZRotate);
}
CPPUNIT_TEST_FIXTURE(TestScene3d, test_perspectiveContrastingLeft)
{
// The file contains a shape with the preset camera perspectiveContrastingLeft.
// Such shape cannot be produced in the UI of LibreOffice, but it can be rendered.
loadFromFile(u"Scene3d_perspectiveContrastingLeft.pptx");
uno::Reference<drawing::XShape> xShape(getShape(0, 0)); // shape 0 on page 0
// Prepare property maps
uno::Reference<beans::XPropertySet> xShapeProps(xShape, uno::UNO_QUERY);
uno::Sequence<beans::PropertyValue> aGeoPropSeq;
xShapeProps->getPropertyValue(u"CustomShapeGeometry"_ustr) >>= aGeoPropSeq;
comphelper::SequenceAsHashMap aGeoPropMap(aGeoPropSeq);
uno::Sequence<beans::PropertyValue> aExtrusionSeq;
aGeoPropMap.getValue(u"Extrusion"_ustr) >>= aExtrusionSeq;
comphelper::SequenceAsHashMap aExtrusionPropMap(aExtrusionSeq);
// projection mode should be PERSPECTIVE
drawing::ProjectionMode eProjectionMode = drawing::ProjectionMode_PARALLEL;
aExtrusionPropMap.getValue(u"ProjectionMode"_ustr) >>= eProjectionMode;
CPPUNIT_ASSERT_EQUAL(drawing::ProjectionMode_PERSPECTIVE, eProjectionMode);
// A perspective projection needs a viewPoint. MS Office has it on the z-axis.
drawing::Position3D aViewPoint;
aExtrusionPropMap.getValue(u"ViewPoint"_ustr) >>= aViewPoint;
CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, aViewPoint.PositionX, 1E-12);
CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, aViewPoint.PositionY, 1E-12);
CPPUNIT_ASSERT_DOUBLES_EQUAL(38451.0, aViewPoint.PositionZ, 1E-12);
// Check rotation angles
drawing::EnhancedCustomShapeParameterPair aParaPair;
aExtrusionPropMap.getValue(UNO_NAME_MISC_OBJ_ROTATEANGLE) >>= aParaPair;
CPPUNIT_ASSERT_DOUBLES_EQUAL(-6.94093344831102, aParaPair.First.Value.get<double>(), 1E-12);
CPPUNIT_ASSERT_DOUBLES_EQUAL(44.4431265782766, aParaPair.Second.Value.get<double>(), 1E-12);
sal_Int32 nZRotate; // unit 1/100 degree
xShapeProps->getPropertyValue(UNO_NAME_MISC_OBJ_ROTATEANGLE) >>= nZRotate;
CPPUNIT_ASSERT_EQUAL(sal_Int32(35504), nZRotate);
// Use BoundRect as workaround, because there is no easy way to test the rendered line geometry.
// The tolerance 10 is estimated and can be adjusted if required for HiDPI.
awt::Rectangle aBoundRect;
xShapeProps->getPropertyValue(UNO_NAME_MISC_OBJ_BOUNDRECT) >>= aBoundRect;
CPPUNIT_ASSERT_DOUBLES_EQUAL(6614, aBoundRect.Width, 10);
CPPUNIT_ASSERT_DOUBLES_EQUAL(8577, aBoundRect.Height, 10);
CPPUNIT_ASSERT_DOUBLES_EQUAL(5725, aBoundRect.X, 10);
CPPUNIT_ASSERT_DOUBLES_EQUAL(4213, aBoundRect.Y, 10);
}
CPPUNIT_TEST_FIXTURE(TestScene3d, test_legacyPerspectiveTopRight)
{
// The file contains a shape with the preset camera legacyPerspectiveTopLeft. These kind of
// camera corresponds directly to the extrusions available in the UI of LibreOffice. The
// non-frontal view is not done by rotation but by shifting the view point horizontal and
// vertical. That is tested here.
loadFromFile(u"Scene3d_legacyPerspectiveTopRight.pptx");
uno::Reference<drawing::XShape> xShape(getShape(0, 0)); // shape 0 on page 0
// Prepare property maps
uno::Reference<beans::XPropertySet> xShapeProps(xShape, uno::UNO_QUERY);
uno::Sequence<beans::PropertyValue> aGeoPropSeq;
xShapeProps->getPropertyValue(u"CustomShapeGeometry"_ustr) >>= aGeoPropSeq;
comphelper::SequenceAsHashMap aGeoPropMap(aGeoPropSeq);
uno::Sequence<beans::PropertyValue> aExtrusionSeq;
aGeoPropMap.getValue(u"Extrusion"_ustr) >>= aExtrusionSeq;
comphelper::SequenceAsHashMap aExtrusionPropMap(aExtrusionSeq);
// shifted view point
drawing::Position3D aViewPoint;
aExtrusionPropMap.getValue(u"ViewPoint"_ustr) >>= aViewPoint;
CPPUNIT_ASSERT_DOUBLES_EQUAL(3472.0, aViewPoint.PositionX, 1E-12);
CPPUNIT_ASSERT_DOUBLES_EQUAL(-3472.0, aViewPoint.PositionY, 1E-12);
CPPUNIT_ASSERT_DOUBLES_EQUAL(25000.0, aViewPoint.PositionZ, 1E-12);
// Use BoundRect as workaround, because there is no easy way to test the rendered line geometry.
// The tolerance 10 is estimated and can be adjusted if required for HiDPI.
awt::Rectangle aBoundRect;
xShapeProps->getPropertyValue(UNO_NAME_MISC_OBJ_BOUNDRECT) >>= aBoundRect;
CPPUNIT_ASSERT_DOUBLES_EQUAL(7004, aBoundRect.Width, 10);
CPPUNIT_ASSERT_DOUBLES_EQUAL(7004, aBoundRect.Height, 10);
CPPUNIT_ASSERT_DOUBLES_EQUAL(6292, aBoundRect.X, 10);
CPPUNIT_ASSERT_DOUBLES_EQUAL(3138, aBoundRect.Y, 10);
}
CPPUNIT_TEST_FIXTURE(TestScene3d, test_lightRig_modernCamera)
{
// The modern camera 'orthographicFront' looks at the shape. The scene is lit by lightRig 'twoPt'.
// A modern camera moves around the shape. The lightRig has a fixed position in regard to the
// shape. This is the same shape as in test_lightRig_legacyCamera but with a modern camera.
// The test assumes rendering with ShadeMode_FLAT.
loadFromFile(u"Scene3d_lightRig_modernCamera.pptx");
BitmapEx aBMP;
getShapeAsBitmap(aBMP, 0);
// Size in pixel depends on dpi. Thus calculate positions relative to size.
// Color in center
sal_Int32 nX = 0.5 * aBMP.GetSizePixel().Width();
sal_Int32 nY = 0.5 * aBMP.GetSizePixel().Height();
lcl_AssertColorsApproximateEqual(::Color(247, 225, 211), aBMP.GetPixelColor(nX, nY));
// Color left
nX = 0.046122 * aBMP.GetSizePixel().Width();
nY = 0.5 * aBMP.GetSizePixel().Height();
lcl_AssertColorsApproximateEqual(::Color(0, 103, 47), aBMP.GetPixelColor(nX, nY));
}
CPPUNIT_TEST_FIXTURE(TestScene3d, test_lightRig_legacyCamera)
{
// The legacy camera 'legacyObliqueFront' looks at the shape. The scene is lit by lightRig
// 'twoPt'. A legacy camera is fix, instead the shape is rotated. The lightRig has a fixed
// position in regard to the camera, but the shape receives various lighting when rotated.
// This is the same shape as in test_lightRig_modernCamera but with a legacy camera.
// The test assumes rendering with ShadeMode_FLAT.
loadFromFile(u"Scene3d_lightRig_legacyCamera.pptx");
BitmapEx aBMP;
getShapeAsBitmap(aBMP, 0);
// Size in pixel depends on dpi. Thus calculate positions relative to size.
// Color in center
sal_Int32 nX = 0.5 * aBMP.GetSizePixel().Width();
sal_Int32 nY = 0.5 * aBMP.GetSizePixel().Height();
lcl_AssertColorsApproximateEqual(::Color(94, 86, 80), aBMP.GetPixelColor(nX, nY));
// Color left
nX = 0.046122 * aBMP.GetSizePixel().Width();
nY = 0.5 * aBMP.GetSizePixel().Height();
lcl_AssertColorsApproximateEqual(::Color(5, 185, 87), aBMP.GetPixelColor(nX, nY));
}
CPPUNIT_TEST_FIXTURE(TestScene3d, test_lightRig_default)
{
// The scene uses the modern camera 'isometricOffAxis1Top' and the lightRig 'harsh'. Here the
// unrotated lightRig is tested. Since rig 'harsh' has only two lights and the direction of the
// second light is against view direction, the colors are same in LibreOffice and MS Office.
// The test assumes rendering with ShadeMode_FLAT.
loadFromFile(u"Scene3d_lightRig_default.pptx");
BitmapEx aBMP;
getShapeAsBitmap(aBMP, 0);
// Size in pixel depends on dpi. Thus calculate positions relative to size.
// Front color
sal_Int32 nX = 0.93811 * aBMP.GetSizePixel().Width();
sal_Int32 nY = 0.49904 * aBMP.GetSizePixel().Height();
lcl_AssertColorsApproximateEqual(::Color(165, 187, 150), aBMP.GetPixelColor(nX, nY));
// Left color
nX = 0.078176 * aBMP.GetSizePixel().Width();
nY = 0.49904 * aBMP.GetSizePixel().Height();
lcl_AssertColorsApproximateEqual(::Color(255, 189, 74), aBMP.GetPixelColor(nX, nY));
// Top color
nX = 0.48860 * aBMP.GetSizePixel().Width();
nY = 0.069098 * aBMP.GetSizePixel().Height();
lcl_AssertColorsApproximateEqual(::Color(189, 100, 39), aBMP.GetPixelColor(nX, nY));
}
CPPUNIT_TEST_FIXTURE(TestScene3d, test_lightRig_dir_rotation)
{
// The scene uses the modern camera 'isometricOffAxis1Top' and the lightRig 'harsh'. The rig is
// rotated around the z-axis by attribute 'dir'. Since rig 'harsh' has only two lights and the
// direction of the second light is against the view direction, colors are same in LibreOffice
// and MSO. The test assumes rendering with ShadeMode_FLAT.
loadFromFile(u"Scene3d_lightRig_dir_rotation.pptx");
BitmapEx aBMP;
getShapeAsBitmap(aBMP, 0);
// Size in pixel depends on dpi. Thus calculate positions relative to size.
// Front color
sal_Int32 nX = 0.93811 * aBMP.GetSizePixel().Width();
sal_Int32 nY = 0.49904 * aBMP.GetSizePixel().Height();
lcl_AssertColorsApproximateEqual(::Color(165, 187, 150), aBMP.GetPixelColor(nX, nY));
// Left color
nX = 0.078176 * aBMP.GetSizePixel().Width();
nY = 0.49904 * aBMP.GetSizePixel().Height();
lcl_AssertColorsApproximateEqual(::Color(206, 108, 42), aBMP.GetPixelColor(nX, nY));
// Top color
nX = 0.48860 * aBMP.GetSizePixel().Width();
nY = 0.069098 * aBMP.GetSizePixel().Height();
lcl_AssertColorsApproximateEqual(::Color(255, 189, 74), aBMP.GetPixelColor(nX, nY));
}
CPPUNIT_TEST_FIXTURE(TestScene3d, test_lightRig_rot_rotation)
{
// The scene uses the modern camera 'isometricOffAxis1Top' and the lightRig 'harsh'. The rig is
// rotated around x- and y-axis by element 'rot'.
// The test assumes rendering with ShadeMode_FLAT.
loadFromFile(u"Scene3d_lightRig_rot_rotation.pptx");
BitmapEx aBMP;
getShapeAsBitmap(aBMP, 0);
// Size in pixel depends on dpi. Thus calculate positions relative to size.
// Front color, same as in MS Office
sal_Int32 nX = 0.93811 * aBMP.GetSizePixel().Width();
sal_Int32 nY = 0.49904 * aBMP.GetSizePixel().Height();
lcl_AssertColorsApproximateEqual(::Color(88, 100, 80), aBMP.GetPixelColor(nX, nY));
// Left color
nX = 0.078176 * aBMP.GetSizePixel().Width();
nY = 0.49904 * aBMP.GetSizePixel().Height();
lcl_AssertColorsApproximateEqual(::Color(255, 214, 99), aBMP.GetPixelColor(nX, nY));
// Top color
nX = 0.48860 * aBMP.GetSizePixel().Width();
nY = 0.069098 * aBMP.GetSizePixel().Height();
lcl_AssertColorsApproximateEqual(::Color(106, 56, 22), aBMP.GetPixelColor(nX, nY));
}
CPPUNIT_TEST_FIXTURE(TestScene3d, test_material_highlight)
{
// The file contains six shapes with same geometry and fill and line color. The scenes use the
// camera 'orthographicFront' and the lightRig 'twoPt'. The test looks at an area of highlight
// and at an area outside the highlight.
loadFromFile(u"Scene3d_material_highlight.pptx");
BitmapEx aBMP;
getShapeAsBitmap(aBMP, 0); // material legacyPlastic
sal_Int32 nX = 0.75 * aBMP.GetSizePixel().Width();
sal_Int32 nYhigh = 0.25 * aBMP.GetSizePixel().Height();
sal_Int32 nYsoft = 0.75 * aBMP.GetSizePixel().Height();
lcl_AssertColorsApproximateEqual(::Color(255, 255, 255), aBMP.GetPixelColor(nX, nYhigh));
lcl_AssertColorsApproximateEqual(::Color(130, 95, 70), aBMP.GetPixelColor(nX, nYsoft));
// same geometry, thus nX, nYhigh and nYsoft unchanged
getShapeAsBitmap(aBMP, 1); // material warmMatte
lcl_AssertColorsApproximateEqual(::Color(253, 200, 164), aBMP.GetPixelColor(nX, nYhigh));
lcl_AssertColorsApproximateEqual(::Color(132, 96, 71), aBMP.GetPixelColor(nX, nYsoft));
getShapeAsBitmap(aBMP, 2); // material metal
lcl_AssertColorsApproximateEqual(::Color(255, 255, 255), aBMP.GetPixelColor(nX, nYhigh));
lcl_AssertColorsApproximateEqual(::Color(132, 96, 71), aBMP.GetPixelColor(nX, nYsoft));
getShapeAsBitmap(aBMP, 3); // material matte
lcl_AssertColorsApproximateEqual(::Color(190, 138, 102), aBMP.GetPixelColor(nX, nYhigh));
lcl_AssertColorsApproximateEqual(::Color(130, 95, 70), aBMP.GetPixelColor(nX, nYsoft));
getShapeAsBitmap(aBMP, 4); // material dkEdge
lcl_AssertColorsApproximateEqual(::Color(255, 255, 255), aBMP.GetPixelColor(nX, nYhigh));
lcl_AssertColorsApproximateEqual(::Color(115, 84, 62), aBMP.GetPixelColor(nX, nYsoft));
getShapeAsBitmap(aBMP, 5); // material legacyMetal
lcl_AssertColorsApproximateEqual(::Color(255, 255, 220), aBMP.GetPixelColor(nX, nYhigh));
lcl_AssertColorsApproximateEqual(::Color(86, 63, 46), aBMP.GetPixelColor(nX, nYsoft));
}
CPPUNIT_TEST_FIXTURE(TestScene3d, test_material_wireframe)
{
// Given a document with a shape in 3D mode with material legacyWireframe.
// It uses a projection "Oblique: Top Left".
loadFromFile(u"Scene3d_material_wireframe.pptx");
uno::Reference<drawing::XShape> xShape(getShape(0, 0));
// Make sure the export to ODF has the corresponding attributes.
save(u"impress8"_ustr);
xmlDocUniquePtr pXmlDoc = parseExport(u"content.xml"_ustr);
assertXPath(pXmlDoc,
"/office:document-content/office:body/office:presentation/draw:page/"
"draw:custom-shape/draw:enhanced-geometry",
"extrusion-origin", u"-0.5 -0.5");
assertXPath(pXmlDoc,
"/office:document-content/office:body/office:presentation/draw:page/"
"draw:custom-shape/draw:enhanced-geometry",
"extrusion-skew", u"30 -45");
assertXPath(pXmlDoc,
"/office:document-content/office:body/office:presentation/draw:page/"
"draw:custom-shape/draw:enhanced-geometry",
"projection", u"parallel");
assertXPath(pXmlDoc,
"/office:document-content/office:body/office:presentation/draw:page/"
"draw:custom-shape/draw:enhanced-geometry",
"shade-mode", u"draft");
}
CPPUNIT_TEST_FIXTURE(TestScene3d, test_cropped_image)
{
// The file contains an image that has been cropped to shape in PowerPoint. The image is in 3D
// mode with perspective camera perspectiveContrastingRightFacing and perspective angle 120°. The
// extrusion is 76200 EMU deep with extrusion color #00B050. As a cropped image, it was imported
// in earlier LO versions as custom shape with bitmap fill, but without any 3D.
loadFromFile(u"Scene3d_cropped_image.pptx");
uno::Reference<drawing::XShape> xShape(getShape(0, 0)); // shape 0 on page 0
// Prepare property maps
uno::Reference<beans::XPropertySet> xShapeProps(xShape, uno::UNO_QUERY);
uno::Sequence<beans::PropertyValue> aGeoPropSeq;
xShapeProps->getPropertyValue(u"CustomShapeGeometry"_ustr) >>= aGeoPropSeq;
comphelper::SequenceAsHashMap aGeoPropMap(aGeoPropSeq);
uno::Sequence<beans::PropertyValue> aExtrusionSeq;
aGeoPropMap.getValue(u"Extrusion"_ustr) >>= aExtrusionSeq;
comphelper::SequenceAsHashMap aExtrusionPropMap(aExtrusionSeq);
// Check that extrusion is on and has the correct geometry.
bool bIsExtruded(false);
aExtrusionPropMap.getValue(u"Extrusion"_ustr) >>= bIsExtruded;
CPPUNIT_ASSERT_MESSAGE("error: Extrusion is disabled", bIsExtruded);
drawing::ProjectionMode eProjectionMode = drawing::ProjectionMode_PARALLEL;
aExtrusionPropMap.getValue(u"ProjectionMode"_ustr) >>= eProjectionMode;
CPPUNIT_ASSERT_EQUAL(drawing::ProjectionMode_PERSPECTIVE, eProjectionMode);
drawing::Position3D aViewPoint;
aExtrusionPropMap.getValue(u"ViewPoint"_ustr) >>= aViewPoint;
CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, aViewPoint.PositionX, 1E-12);
CPPUNIT_ASSERT_DOUBLES_EQUAL(0.0, aViewPoint.PositionY, 1E-12);
CPPUNIT_ASSERT_DOUBLES_EQUAL(9223.7479, aViewPoint.PositionZ, 1E-5);
// Check shape rotation angles
drawing::EnhancedCustomShapeParameterPair aParaPair;
aExtrusionPropMap.getValue(UNO_NAME_MISC_OBJ_ROTATEANGLE) >>= aParaPair;
CPPUNIT_ASSERT_DOUBLES_EQUAL(-6.94093344831102, aParaPair.First.Value.get<double>(), 1E-12);
CPPUNIT_ASSERT_DOUBLES_EQUAL(-44.4431265782766, aParaPair.Second.Value.get<double>(), 1E-12);
sal_Int32 nZRotate; // unit 1/100 degree
xShapeProps->getPropertyValue(UNO_NAME_MISC_OBJ_ROTATEANGLE) >>= nZRotate;
CPPUNIT_ASSERT_EQUAL(sal_Int32(496), nZRotate);
// Check extrusion depth and color
aExtrusionPropMap.getValue(u"Depth"_ustr) >>= aParaPair;
CPPUNIT_ASSERT_DOUBLES_EQUAL(212.0, aParaPair.First.Value.get<double>(), 1E-12);
Color nColor;
xShapeProps->getPropertyValue(UNO_NAME_FILLCOLOR_2) >>= nColor;
CPPUNIT_ASSERT_EQUAL(Color(0x00b050), nColor);
}
CPPUNIT_TEST_FIXTURE(TestScene3d, test_pureImage)
{
// Given a document with a scene3d element on an image. Because it would lose image properties
// it is currently (March 2024) not imported as custom shape. But the z-rotation is evaluated
// as users might have used this instead of shape rotation, for example.
loadFromFile(u"Scene3d_pureImage.pptx");
uno::Reference<drawing::XShape> xShape(getShape(0, 0)); // shape 0 on page 0
// Make sure it is an image.
CPPUNIT_ASSERT_EQUAL(u"com.sun.star.drawing.GraphicObjectShape"_ustr, xShape->getShapeType());
// Make sure the image is rotated.
uno::Reference<beans::XPropertySet> xShapeProps(xShape, uno::UNO_QUERY);
sal_Int32 nZRotate; // unit 1/100 degree
xShapeProps->getPropertyValue(UNO_NAME_MISC_OBJ_ROTATEANGLE) >>= nZRotate;
CPPUNIT_ASSERT_EQUAL(sal_Int32(27000), nZRotate);
}
CPPUNIT_TEST_FIXTURE(TestScene3d, test_shape_rotation)
{
// Given a document with a shape in 3D mode with shape rotation rot="300000".
loadFromFile(u"Scene3d_shape_rotation.pptx");
uno::Reference<drawing::XShape> xShape(getShape(0, 0));
// Make sure that the immediate export to pptx has the same shape rotation
save(u"Impress Office Open XML"_ustr);
xmlDocUniquePtr pXmlDoc = parseExport(u"ppt/slides/slide1.xml"_ustr);
assertXPath(pXmlDoc, "//p:spPr/a:xfrm", "rot", u"300000");
}
CPPUNIT_PLUGIN_IMPLEMENT();
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */