tdf#159626 vml pattern import: add color, fix back/foreground

This depends on tdf#126533 which imports page style v:fill,
BUT ONLY IN ORDER TO support the unit tests.
The patch itself can stand alone
and fixes vml import into textboxes/shapes etc.
i.e. backporting could be possible by dropping the unit tests.

The pattern that VML uses to indicate foreground
and background is very different from what LO needs.
[Fortunately LO does not use the _guess_ from
vcl::bitmap::isHistorical8x8 to determine which
color is the background. Instead it always uses the first pixel.]

Documentation says that unspecified XML_fillcolor
and XML_color should be white, but observation
says it should be 25% gray (Word 2003).
25% gray == C0C0C0 == fillcolor="silver" == COL_LIGHTGRAY

Currently, we simply export as a colored, tiled image,
and not as a B&W type="pattern"
so no corresponding export changes need to be made to export.

Existing unit test documents that are affected:
-chart2export's PieChartDataLabels.docx (page background)
-ooxmlexport5's fdo77725.docx (minimized PieChartDataLabels.docx)
 * both foreground and background are set to white => solid white
-sw/qa/core/data/ooxml/pass/fdo79131.docx (shape "inline")

make CppunitTest_sw_tiledrendering \
    CPPUNIT_TEST_NAME=testTdf159626_yellowPatternFill
make CppunitTest_sw_tiledrendering \
    CPPUNIT_TEST_NAME=testTdf159626_yellowPatternFillB
make CppunitTest_sw_tiledrendering \
    CPPUNIT_TEST_NAME=testTdf159626_blackPatternFill

Change-Id: I9533ac4a7489081ffc62a10e900f5526abb906db
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/163106
Tested-by: Jenkins
Reviewed-by: Justin Luth <jluth@mail.com>
diff --git a/oox/source/vml/vmlformatting.cxx b/oox/source/vml/vmlformatting.cxx
index 029d542..6182950 100644
--- a/oox/source/vml/vmlformatting.cxx
+++ b/oox/source/vml/vmlformatting.cxx
@@ -29,6 +29,7 @@
#include <com/sun/star/drawing/EnhancedCustomShapeTextPathMode.hpp>
#include <com/sun/star/table/ShadowFormat.hpp>
#include <com/sun/star/text/XTextRange.hpp>

#include <o3tl/float_int_conversion.hxx>
#include <o3tl/unit_conversion.hxx>
#include <rtl/strbuf.hxx>
@@ -46,6 +47,8 @@
#include <svx/svdtrans.hxx>
#include <comphelper/propertysequence.hxx>
#include <o3tl/string_view.hxx>
#include <svx/xbitmap.hxx>
#include <vcl/BitmapTools.hxx>
#include <vcl/virdev.hxx>

namespace oox::vml {
@@ -843,6 +846,51 @@ void FillModel::pushToPropMap( ShapePropertyMap& rPropMap, const GraphicHelper& 
                    aFillProps.maBlipProps.mxFillGraphic = rGraphicHelper.importEmbeddedGraphic(moBitmapPath.value());
                    if (aFillProps.maBlipProps.mxFillGraphic.is())
                    {
                        if (nFillType == XML_pattern)
                        {
                            // VML provides an 8x8 black(background) and white(foreground) pattern
                            // along with specified background(color2) and foreground(color) colors,
                            // while LO needs the color applied directly to the pattern.
                            const Graphic aGraphic(aFillProps.maBlipProps.mxFillGraphic);
                            ::Color nBackColor;
                            ::Color nPixelColor;
                            bool bIs8x8 = vcl::bitmap::isHistorical8x8(aGraphic.GetBitmapEx(),
                                                                       nBackColor, nPixelColor);
                            if (bIs8x8)
                            {
                                nBackColor
                                    = ConversionHelper::decodeColor(rGraphicHelper, moColor2,
                                                                    moOpacity2, API_RGB_WHITE)
                                          .getColor(rGraphicHelper);
                                // Documentation says undefined == white; observation says lightgray
                                nPixelColor
                                    = ConversionHelper::decodeColor(rGraphicHelper, moColor,
                                                                    moOpacity, COL_LIGHTGRAY)
                                          .getColor(rGraphicHelper);

                                XOBitmap aXOB(aGraphic.GetBitmapEx());
                                aXOB.Bitmap2Array();
                                // LO uses the first pixel's color to represent background pixels
                                if (aXOB.GetBackgroundColor() == COL_WHITE)
                                {
                                    // White always represents the foreground in VML => swap
                                    aXOB.SetPixelColor(nBackColor);
                                    aXOB.SetBackgroundColor(nPixelColor);
                                }
                                else
                                {
                                    assert(aXOB.GetBackgroundColor() == COL_BLACK);
                                    aXOB.SetPixelColor(nPixelColor);
                                    aXOB.SetBackgroundColor(nBackColor);
                                }
                                aXOB.Array2Bitmap();

                                Graphic aLOPattern(aXOB.GetBitmap());
                                aLOPattern.setOriginURL(aGraphic.getOriginURL());
                                aFillProps.maBlipProps.mxFillGraphic = aLOPattern.GetXGraphic();
                            }
                        }

                        aFillProps.moFillType = XML_blipFill;
                        aFillProps.maBlipProps.moBitmapMode = (nFillType == XML_frame) ? XML_stretch : XML_tile;
                        break;  // do not break if bitmap is missing, but run to XML_solid instead
diff --git a/sw/qa/extras/tiledrendering/data/tdf159626_blackPatternFill.docx b/sw/qa/extras/tiledrendering/data/tdf159626_blackPatternFill.docx
new file mode 100644
index 0000000..650f430
--- /dev/null
+++ b/sw/qa/extras/tiledrendering/data/tdf159626_blackPatternFill.docx
Binary files differ
diff --git a/sw/qa/extras/tiledrendering/data/tdf159626_yellowPatternFill.docx b/sw/qa/extras/tiledrendering/data/tdf159626_yellowPatternFill.docx
new file mode 100644
index 0000000..9be8c27
--- /dev/null
+++ b/sw/qa/extras/tiledrendering/data/tdf159626_yellowPatternFill.docx
Binary files differ
diff --git a/sw/qa/extras/tiledrendering/data/tdf159626_yellowPatternFillB.docx b/sw/qa/extras/tiledrendering/data/tdf159626_yellowPatternFillB.docx
new file mode 100644
index 0000000..f802513
--- /dev/null
+++ b/sw/qa/extras/tiledrendering/data/tdf159626_yellowPatternFillB.docx
Binary files differ
diff --git a/sw/qa/extras/tiledrendering/tiledrendering.cxx b/sw/qa/extras/tiledrendering/tiledrendering.cxx
index 8dccfeb..670f7be 100644
--- a/sw/qa/extras/tiledrendering/tiledrendering.cxx
+++ b/sw/qa/extras/tiledrendering/tiledrendering.cxx
@@ -4165,6 +4165,103 @@ CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testSwitchingChartToDarkMode)
    CPPUNIT_ASSERT(nBlackPixels > nWhitePixels);
}

CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testTdf159626_yellowPatternFill)
{
    SwXTextDocument* pXTextDocument = createDoc("tdf159626_yellowPatternFill.docx");
    CPPUNIT_ASSERT(pXTextDocument);

    SwDoc* pDoc = pXTextDocument->GetDocShell()->GetDoc();
    SwView* pView = pDoc->GetDocShell()->GetView();
    uno::Reference<frame::XFrame> xFrame = pView->GetViewFrame().GetFrame().GetFrameInterface();

    Bitmap aBitmap(getTile(pXTextDocument));
    Size aSize = aBitmap.GetSizePixel();

    int nPureYellowPixels = 0;
    int nEdgePlusGrayPlusAntialiasPixels = 0;
    BitmapScopedReadAccess pAccess(aBitmap);
    for (tools::Long x = 0; x < aSize.Width(); ++x)
    {
        for (tools::Long y = 0; y < aSize.Height(); ++y)
        {
            Color aActualColor(pAccess->GetPixel(y, x));
            if (aActualColor == COL_YELLOW)
                ++nPureYellowPixels;
            else
                ++nEdgePlusGrayPlusAntialiasPixels;
        }
    }
    // The page background pattern is 62 yellow/2 gray pixels - first pixel is gray(foreground)
    // Without the patch, the document was primarily gray.
    CPPUNIT_ASSERT(nPureYellowPixels > 0);
    CPPUNIT_ASSERT(nPureYellowPixels / 2 > nEdgePlusGrayPlusAntialiasPixels);
}

CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testTdf159626_yellowPatternFillB)
{
    SwXTextDocument* pXTextDocument = createDoc("tdf159626_yellowPatternFillB.docx");
    CPPUNIT_ASSERT(pXTextDocument);

    SwDoc* pDoc = pXTextDocument->GetDocShell()->GetDoc();
    SwView* pView = pDoc->GetDocShell()->GetView();
    uno::Reference<frame::XFrame> xFrame = pView->GetViewFrame().GetFrame().GetFrameInterface();

    Bitmap aBitmap(getTile(pXTextDocument));
    Size aSize = aBitmap.GetSizePixel();

    int nPureYellowPixels = 0;
    int nEdgePlusGrayPlusAntialiasPixels = 0;
    BitmapScopedReadAccess pAccess(aBitmap);
    for (tools::Long x = 0; x < aSize.Width(); ++x)
    {
        for (tools::Long y = 0; y < aSize.Height(); ++y)
        {
            Color aActualColor(pAccess->GetPixel(y, x));
            if (aActualColor == COL_YELLOW)
                ++nPureYellowPixels;
            else
                ++nEdgePlusGrayPlusAntialiasPixels;
        }
    }
    // The page background pattern is 62 yellow/2 gray pixels - first pixel is yellow(background)
    // LO already imported this correctly, as primarily yellow - ensure it stays that way.
    CPPUNIT_ASSERT(nPureYellowPixels > 0);
    CPPUNIT_ASSERT(nPureYellowPixels / 2 > nEdgePlusGrayPlusAntialiasPixels);
}

CPPUNIT_TEST_FIXTURE(SwTiledRenderingTest, testTdf159626_blackPatternFill)
{
    SwXTextDocument* pXTextDocument = createDoc("tdf159626_blackPatternFill.docx");
    CPPUNIT_ASSERT(pXTextDocument);

    SwDoc* pDoc = pXTextDocument->GetDocShell()->GetDoc();
    SwView* pView = pDoc->GetDocShell()->GetView();
    uno::Reference<frame::XFrame> xFrame = pView->GetViewFrame().GetFrame().GetFrameInterface();

    Bitmap aBitmap(getTile(pXTextDocument));
    Size aSize = aBitmap.GetSizePixel();

    int nPureBlackPixels = 0;
    int nEdgePlusWhitePlusAntialiasPixels = 0;
    BitmapScopedReadAccess pAccess(aBitmap);
    for (tools::Long x = 0; x < aSize.Width(); ++x)
    {
        for (tools::Long y = 0; y < aSize.Height(); ++y)
        {
            Color aActualColor(pAccess->GetPixel(y, x));
            if (aActualColor == COL_BLACK)
                ++nPureBlackPixels;
            else
                ++nEdgePlusWhitePlusAntialiasPixels;
        }
    }
    // Both the foreground and background are defined as black, represented by a pattern with
    // 48 white/16 black pixels.
    // The document should be entirely black (except for text margin markings).
    CPPUNIT_ASSERT(nEdgePlusWhitePlusAntialiasPixels > 0);
    CPPUNIT_ASSERT(nPureBlackPixels / 10 > nEdgePlusWhitePlusAntialiasPixels);
}

CPPUNIT_PLUGIN_IMPLEMENT();

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */