WMF tdf#55058 tdf#142722 Add implementation of BitBlt and StretchBlt

With previous implementation, only BitBlt record with 1 bit color depth
was supported and StretchBlt was not implemented at all.

With this commit the support for 1 bit, 24 bit and 32 bit,
for both BitBlt and StretchBlt were added.

Change-Id: I061b2beae8c2f143ddff9c8c8bb64bf52f4cf502
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/116873
Tested-by: Jenkins
Reviewed-by: Bartosz Kosiorek <gang65@poczta.onet.pl>
diff --git a/drawinglayer/source/tools/primitive2dxmldump.cxx b/drawinglayer/source/tools/primitive2dxmldump.cxx
index 295c13f..9ed0e79 100644
--- a/drawinglayer/source/tools/primitive2dxmldump.cxx
+++ b/drawinglayer/source/tools/primitive2dxmldump.cxx
@@ -228,8 +228,22 @@ void Primitive2dXmlDump::decomposeAndWrite(

                rWriter.attribute("height", rSizePixel.getHeight());
                rWriter.attribute("width", rSizePixel.getWidth());
                rWriter.attribute("checksum", aBitmapEx.GetChecksum());
                rWriter.attribute("checksum", OString(std::to_string( aBitmapEx.GetChecksum() )));

                for (tools::Long y=0; y<rSizePixel.getHeight(); y++)
                {

                    rWriter.startElement("data");
                    OUString aBitmapData = "";
                    for (tools::Long x=0; x<rSizePixel.getHeight(); x++)
                    {
                        if (x !=0)
                            aBitmapData = aBitmapData + ",";
                        aBitmapData = aBitmapData + aBitmapEx.GetPixelColor(x, y).AsRGBHexString();
                    }
                    rWriter.attribute("row", aBitmapData);
                    rWriter.endElement();
                }
                rWriter.endElement();
            }
            break;
diff --git a/emfio/qa/cppunit/emf/EmfImportTest.cxx b/emfio/qa/cppunit/emf/EmfImportTest.cxx
index d949240..8c561902 100644
--- a/emfio/qa/cppunit/emf/EmfImportTest.cxx
+++ b/emfio/qa/cppunit/emf/EmfImportTest.cxx
@@ -62,6 +62,7 @@ class Test : public test::BootstrapFixture, public XmlTestTools, public unotest:
    void TestFillRegion();
    void TestExtTextOutOpaqueAndClipTransform();

    void TestBitBltStretchBltWMF();
    void TestExtTextOutOpaqueAndClipWMF();
    void TestPaletteWMF();
    void TestRoundrectWMF();
@@ -96,6 +97,8 @@ public:
    CPPUNIT_TEST(TestDrawPolyLine16WithClip);
    CPPUNIT_TEST(TestFillRegion);
    CPPUNIT_TEST(TestExtTextOutOpaqueAndClipTransform);

    CPPUNIT_TEST(TestBitBltStretchBltWMF);
    CPPUNIT_TEST(TestExtTextOutOpaqueAndClipWMF);
    CPPUNIT_TEST(TestPaletteWMF);
    CPPUNIT_TEST(TestRoundrectWMF);
@@ -511,7 +514,6 @@ void Test::TestPolylinetoCloseStroke()
                "color", "#000000");
}


void Test::TestExtTextOutOpaqueAndClipTransform()
{
    // tdf#142495 EMF records: SETBKCOLOR, SELECTOBJECT, EXTTEXTOUTW, MODIFYWORLDTRANSFORM, CREATEFONTINDIRECT.
@@ -521,7 +523,6 @@ void Test::TestExtTextOutOpaqueAndClipTransform()
    xmlDocUniquePtr pDocument = dumper.dumpAndParse(comphelper::sequenceToContainer<Primitive2DContainer>(aSequence));
    CPPUNIT_ASSERT (pDocument);


    assertXPath(pDocument, "/primitive2D/metafile/transform/textsimpleportion", 2);
    assertXPath(pDocument, "/primitive2D/metafile/transform/textsimpleportion[1]",
                "text", "No_rect- DLP-");
@@ -576,6 +577,73 @@ void Test::TestExtTextOutOpaqueAndClipTransform()
                "fontcolor", "#000000");
}

void Test::TestBitBltStretchBltWMF()
{
    // tdf#55058 tdf#142722 WMF records: BITBLT, STRETCHBLT.
    Primitive2DSequence aSequence = parseEmf(u"/emfio/qa/cppunit/wmf/data/TestBitBltStretchBlt.wmf");
    CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(aSequence.getLength()));
    drawinglayer::Primitive2dXmlDump dumper;
    xmlDocUniquePtr pDocument = dumper.dumpAndParse(comphelper::sequenceToContainer<Primitive2DContainer>(aSequence));
    CPPUNIT_ASSERT (pDocument);

    assertXPath(pDocument, "/primitive2D/metafile/transform/bitmap", 2);
    assertXPath(pDocument, "/primitive2D/metafile/transform/bitmap[1]",
                "xy11", "508");
    assertXPath(pDocument, "/primitive2D/metafile/transform/bitmap[1]",
                "xy12", "0");
    assertXPath(pDocument, "/primitive2D/metafile/transform/bitmap[1]",
                "xy13", "711");
    assertXPath(pDocument, "/primitive2D/metafile/transform/bitmap[1]",
                "xy21", "0");
    assertXPath(pDocument, "/primitive2D/metafile/transform/bitmap[1]",
                "xy22", "508");
    assertXPath(pDocument, "/primitive2D/metafile/transform/bitmap[1]",
                "xy23", "508");
    assertXPath(pDocument, "/primitive2D/metafile/transform/bitmap[1]",
                "height", "10");
    assertXPath(pDocument, "/primitive2D/metafile/transform/bitmap[1]",
                "width", "10");
#if !defined(MACOSX) && !defined(_WIN32) // TODO Bitmap display needs to be aligned for macOS and Windows
    assertXPath(pDocument, "/primitive2D/metafile/transform/bitmap[1]",
                "checksum", "747141214295528493");
#endif
    assertXPath(pDocument, "/primitive2D/metafile/transform/bitmap[1]/data",
                10);
    assertXPath(pDocument, "/primitive2D/metafile/transform/bitmap[1]/data[1]",
                "row", "000000,000000,000000,000000,000000,000000,000000,000000,000000,000000");
    assertXPath(pDocument, "/primitive2D/metafile/transform/bitmap[1]/data[4]",
                "row", "000000,ffffff,000000,ffffff,000000,ffffff,000000,ffffff,000000,ffffff");
    assertXPath(pDocument, "/primitive2D/metafile/transform/bitmap[1]/data[5]",
                "row", "ffffff,000000,ffffff,ffffff,000000,000000,000000,ffffff,ffffff,000000");

    assertXPath(pDocument, "/primitive2D/metafile/transform/bitmap[2]",
                "xy11", "1524");
    assertXPath(pDocument, "/primitive2D/metafile/transform/bitmap[2]",
                "xy12", "0");
    assertXPath(pDocument, "/primitive2D/metafile/transform/bitmap[2]",
                "xy13", "1524");
    assertXPath(pDocument, "/primitive2D/metafile/transform/bitmap[2]",
                "xy21", "0");
    assertXPath(pDocument, "/primitive2D/metafile/transform/bitmap[2]",
                "xy22", "1016");
    assertXPath(pDocument, "/primitive2D/metafile/transform/bitmap[2]",
                "xy23", "102");
    assertXPath(pDocument, "/primitive2D/metafile/transform/bitmap[2]",
                "height", "10");
    assertXPath(pDocument, "/primitive2D/metafile/transform/bitmap[2]",
                "width", "10");
#if !defined(MACOSX) && !defined(_WIN32) // TODO Bitmap display needs to be aligned for macOS and Windows
    assertXPath(pDocument, "/primitive2D/metafile/transform/bitmap[2]",
                "checksum", "3134789313661517563");
#endif
    assertXPath(pDocument, "/primitive2D/metafile/transform/bitmap[2]/data",
                10);
    assertXPath(pDocument, "/primitive2D/metafile/transform/bitmap[2]/data[1]",
                "row", "000000,00001c,000038,000055,000071,00008d,0000aa,0000c6,0000e2,0000ff");
    assertXPath(pDocument, "/primitive2D/metafile/transform/bitmap[2]/data[5]",
                "row", "720000,721c1c,723838,725555,727171,72728d,55728d,39728d,1d728d,00728d");
}

void Test::TestExtTextOutOpaqueAndClipWMF()
{
    // tdf#53004 WMF records: SETBKCOLOR, SELECTOBJECT, EXTTEXTOUT, CREATEBRUSHINDIRECT.
@@ -713,10 +781,16 @@ void Test::TestStretchDIBWMF()
                "height", "10");
    assertXPath(pDocument, "/primitive2D/metafile/transform/mask/bitmap",
                "width", "10");
#if !defined(MACOSX) // TODO DIB display needs to be fixed for macOS
#if !defined(MACOSX) // TODO DIB display needs to be aligned for macOS
    assertXPath(pDocument, "/primitive2D/metafile/transform/mask/bitmap",
                "checksum", "275245357");
                "checksum", "14148300367030905133");
#endif
    assertXPath(pDocument, "/primitive2D/metafile/transform/mask/bitmap/data",
                10);
    assertXPath(pDocument, "/primitive2D/metafile/transform//mask/bitmap/data[1]",
                "row", "000000,00001c,000038,000055,000071,00008d,0000aa,0000c6,0000e2,0000ff");
    assertXPath(pDocument, "/primitive2D/metafile/transform//mask/bitmap/data[5]",
                "row", "720000,721c1c,723838,725555,727171,72728d,55728d,39728d,1d728d,00728d");
}

void Test::TestPolyLineWidth()
diff --git a/emfio/qa/cppunit/wmf/data/TestBitBltStretchBlt.wmf b/emfio/qa/cppunit/wmf/data/TestBitBltStretchBlt.wmf
new file mode 100644
index 0000000..030027c
--- /dev/null
+++ b/emfio/qa/cppunit/wmf/data/TestBitBltStretchBlt.wmf
Binary files differ
diff --git a/emfio/source/reader/wmfreader.cxx b/emfio/source/reader/wmfreader.cxx
index 65c2cff..64dbf66 100644
--- a/emfio/source/reader/wmfreader.cxx
+++ b/emfio/source/reader/wmfreader.cxx
@@ -802,29 +802,19 @@ namespace emfio
            break;

            case W_META_BITBLT:
            case W_META_STRETCHBLT:
            {
                // 0-3   : nRasterOperation        #93454#
                // 4-5   : y offset of source bitmap
                // 6-7   : x offset of source bitmap
                // 8-9   : height of source and destination bitmap
                // 10-11 : width  of source and destination bitmap
                // 12-13 : destination position y (in pixel)
                // 14-15 : destination position x (in pixel)
                // 16-17 : bitmap type
                // 18-19 : Width Bitmap in Pixel
                // 20-21 : Height Bitmap in Pixel
                // 22-23 : bytes per scanline
                // 24    : planes
                // 25    : bitcount

                sal_uInt32  nRasterOperation = 0;
                sal_Int16   nYSrc = 0, nXSrc = 0, nSye = 0, nSxe = 0, nBitmapType = 0, nWidth = 0, nHeight = 0, nBytesPerScan = 0;
                sal_Int16   nSrcHeight = 0, nSrcWidth = 0, nYSrc, nXSrc, nSye, nSxe, nBitmapType, nWidth, nHeight, nBytesPerScan;
                sal_uInt8   nPlanes, nBitCount;
                const bool bNoSourceBitmap = ( nRecordSize == ( static_cast< sal_uInt32 >( nFunc ) >> 8 ) + 3 );

                mpInputStream->ReadUInt32( nRasterOperation );
                SAL_INFO("emfio", "\t\t Raster operation: 0x" << std::hex << nRasterOperation << std::dec << ", No source bitmap: " << bNoSourceBitmap);

                if( nFunc == W_META_STRETCHBLT )
                    mpInputStream->ReadInt16( nSrcHeight ).ReadInt16( nSrcWidth );

                mpInputStream->ReadInt16( nYSrc ).ReadInt16( nXSrc );
                if ( bNoSourceBitmap )
                    mpInputStream->SeekRel( 2 ); // Skip Reserved 2 bytes (it must be ignored)
@@ -833,12 +823,13 @@ namespace emfio
                mpInputStream->ReadInt16( nBitmapType ).ReadInt16( nWidth ).ReadInt16( nHeight ).ReadInt16( nBytesPerScan ).ReadUChar( nPlanes ).ReadUChar( nBitCount );

                SAL_INFO("emfio", "\t\t Bitmap type:" << nBitmapType << " Width:" << nWidth << " Height:" << nHeight << " WidthBytes:" << nBytesPerScan << " Planes: " << static_cast< sal_uInt16 >( nPlanes ) << " BitCount: " << static_cast< sal_uInt16 >( nBitCount ) );
                if ( bNoSourceBitmap )
                if ( bNoSourceBitmap || ( nBitCount == 4 ) || ( nBitCount == 8 ) || nPlanes != 1 )
                {
                    SAL_WARN("emfio", "\t\t TODO The unsupported Bitmap record (without embedded source Bitmap). Please fill a bug.");
                    SAL_WARN("emfio", "\t\t TODO The unsupported Bitmap record. Please fill a bug.");
                    break;
                }
                bool bOk = nWidth && nHeight && nBytesPerScan > 0 && nPlanes == 1 && nBitCount == 1;
                const vcl::PixelFormat ePixelFormat = vcl::bitDepthToPixelFormat( nBitCount );
                bool bOk = nWidth && nHeight && nBytesPerScan > 0 && nPlanes == 1 && ePixelFormat != vcl::PixelFormat::INVALID;
                if (bOk)
                {
                    // must be enough data to fulfil the request
@@ -851,25 +842,10 @@ namespace emfio
                }
                if (bOk)
                {
                    vcl::bitmap::RawBitmap aBmp( Size( nWidth, nHeight ), 24 );
                    for (sal_uInt16 y = 0; y < nHeight && mpInputStream->good(); ++y)
                    {
                        sal_uInt16 x = 0;
                        for (sal_uInt16 scan = 0; scan < nBytesPerScan; scan++ )
                        {
                            sal_Int8 nEightPixels = 0;
                            mpInputStream->ReadSChar( nEightPixels );
                            for (sal_Int8 i = 7; i >= 0; i-- )
                            {
                                if ( x < nWidth )
                                {
                                    aBmp.SetPixel( y, x, ((nEightPixels>>i)&1) ? COL_BLACK : COL_WHITE );
                                }
                                x++;
                            }
                        }
                    }
                    BitmapEx aBitmap = vcl::bitmap::CreateFromData(std::move(aBmp));
                    std::unique_ptr< sal_uInt8[] > pData;
                    pData.reset( new sal_uInt8[ nHeight * nBytesPerScan ] );
                    mpInputStream->ReadBytes( pData.get(), nHeight * nBytesPerScan );
                    BitmapEx aBitmap = vcl::bitmap::CreateFromData( pData.get(), nWidth, nHeight, nBytesPerScan, ePixelFormat, true );
                    if ( nSye && nSxe &&
                         ( nXSrc + nSxe <= nWidth ) &&
                         ( nYSrc + nSye <= nHeight ) )
@@ -885,7 +861,6 @@ namespace emfio

            case W_META_DIBBITBLT:
            case W_META_DIBSTRETCHBLT:
            case W_META_STRETCHBLT:
            case W_META_STRETCHDIB:
            {
                sal_uInt32  nRasterOperation = 0;
@@ -902,56 +877,49 @@ namespace emfio
                // nSrcHeight and nSrcWidth is the number of pixels that has to been used
                // If they are set to zero, it is as indicator not to scale the bitmap later
                if( nFunc == W_META_DIBSTRETCHBLT ||
                    nFunc == W_META_STRETCHBLT ||
                    nFunc == W_META_STRETCHDIB )
                    mpInputStream->ReadInt16( nSrcHeight ).ReadInt16( nSrcWidth );

                // nYSrc and nXSrc is the offset of the first pixel
                mpInputStream->ReadInt16( nYSrc ).ReadInt16( nXSrc );

                // TODO Add META_STRETCHBLT support, which is defined by Bitmap16 Object
                if( nFunc == W_META_DIBBITBLT ||
                    nFunc == W_META_DIBSTRETCHBLT ||
                    nFunc == W_META_STRETCHDIB )
                if ( bNoSourceBitmap )
                    mpInputStream->SeekRel( 2 ); // Skip Reserved 2 bytes (it must be ignored)

                Size aDestSize( ReadYXExt() );
                if ( aDestSize.Width() && aDestSize.Height() )  // #92623# do not try to read buggy bitmaps
                {
                    if ( bNoSourceBitmap )
                        mpInputStream->SeekRel( 2 ); // Skip Reserved 2 bytes (it must be ignored)

                    Size aDestSize( ReadYXExt() );
                    if ( aDestSize.Width() && aDestSize.Height() )  // #92623# do not try to read buggy bitmaps
                    tools::Rectangle aDestRect( ReadYX(), aDestSize );
                    if ( !bNoSourceBitmap )
                    {
                        tools::Rectangle aDestRect( ReadYX(), aDestSize );
                        if ( !bNoSourceBitmap )
                        {
                            // tdf#142625 Read the DIBHeader and check if bitmap is supported
                            // If bitmap is not supported don't run ReadDIB, as it will interrupt image processing
                            const auto nOldPos(mpInputStream->Tell());
                            sal_uInt32  nHeaderSize;
                            sal_uInt16  nBitCount;
                            mpInputStream->ReadUInt32( nHeaderSize );
                            if ( nHeaderSize == 0xC ) // BitmapCoreHeader
                                mpInputStream->SeekRel( 6 ); // skip Width (16), Height (16), Planes (16)
                            else
                                mpInputStream->SeekRel( 10 ); // skip Width (32), Height (32), Planes (16)
                            mpInputStream->ReadUInt16( nBitCount );
                            if ( nBitCount == 0 ) // TODO Undefined BitCount (JPEG/PNG), which are not supported
                                break;
                            mpInputStream->Seek(nOldPos);
                        // tdf#142625 Read the DIBHeader and check if bitmap is supported
                        // If bitmap is not supported don't run ReadDIB, as it will interrupt image processing
                        const auto nOldPos(mpInputStream->Tell());
                        sal_uInt32  nHeaderSize;
                        sal_uInt16  nBitCount;
                        mpInputStream->ReadUInt32( nHeaderSize );
                        if ( nHeaderSize == 0xC ) // BitmapCoreHeader
                            mpInputStream->SeekRel( 6 ); // skip Width (16), Height (16), Planes (16)
                        else
                            mpInputStream->SeekRel( 10 ); // skip Width (32), Height (32), Planes (16)
                        mpInputStream->ReadUInt16( nBitCount );
                        if ( nBitCount == 0 ) // TODO Undefined BitCount (JPEG/PNG), which are not supported
                            break;
                        mpInputStream->Seek(nOldPos);

                            if ( !ReadDIB( aBmp, *mpInputStream, false ) )
                                SAL_WARN( "emfio", "\tTODO Read DIB failed. Interrupting processing whole image. Please report bug report." );
                        }
                        // test if it is sensible to crop
                        if ( nSrcHeight && nSrcWidth &&
                             ( nXSrc + nSrcWidth <= aBmp.GetSizePixel().Width() ) &&
                             ( nYSrc + nSrcHeight <= aBmp.GetSizePixel().Height() ) )
                        {
                            tools::Rectangle aCropRect( Point( nXSrc, nYSrc ), Size( nSrcWidth, nSrcHeight ) );
                            aBmp.Crop( aCropRect );
                        }

                        maBmpSaveList.emplace_back(new BSaveStruct(aBmp, aDestRect, nRasterOperation));
                        if ( !ReadDIB( aBmp, *mpInputStream, false ) )
                            SAL_WARN( "emfio", "\tTODO Read DIB failed. Interrupting processing whole image. Please report bug report." );
                    }
                    // test if it is sensible to crop
                    if ( nSrcHeight && nSrcWidth &&
                            ( nXSrc + nSrcWidth <= aBmp.GetSizePixel().Width() ) &&
                            ( nYSrc + nSrcHeight <= aBmp.GetSizePixel().Height() ) )
                    {
                        tools::Rectangle aCropRect( Point( nXSrc, nYSrc ), Size( nSrcWidth, nSrcHeight ) );
                        aBmp.Crop( aCropRect );
                    }

                    maBmpSaveList.emplace_back(new BSaveStruct(aBmp, aDestRect, nRasterOperation));
                }
            }
            break;
diff --git a/include/vcl/BitmapTools.hxx b/include/vcl/BitmapTools.hxx
index fa7a43b..8b1a814 100644
--- a/include/vcl/BitmapTools.hxx
+++ b/include/vcl/BitmapTools.hxx
@@ -46,7 +46,7 @@ void loadFromSvg(SvStream& rStream, const OUString& sPath, BitmapEx& rBitmapEx, 
    @param nStride
    The number of bytes in a scanline, must be >= (width * bitcount / 8)
*/
BitmapEx VCL_DLLPUBLIC CreateFromData( sal_uInt8 const *pData, sal_Int32 nWidth, sal_Int32 nHeight, sal_Int32 nStride, vcl::PixelFormat ePixelFormat);
BitmapEx VCL_DLLPUBLIC CreateFromData( sal_uInt8 const *pData, sal_Int32 nWidth, sal_Int32 nHeight, sal_Int32 nStride, vcl::PixelFormat ePixelFormat, bool bReversColors = false);

BitmapEx VCL_DLLPUBLIC CreateFromData( RawBitmap && data );

diff --git a/vcl/source/bitmap/BitmapTools.cxx b/vcl/source/bitmap/BitmapTools.cxx
index 32204a4..0ebbdae 100644
--- a/vcl/source/bitmap/BitmapTools.cxx
+++ b/vcl/source/bitmap/BitmapTools.cxx
@@ -125,8 +125,10 @@ void loadFromSvg(SvStream& rStream, const OUString& sPath, BitmapEx& rBitmapEx, 
    The block of data to copy
    @param nStride
    The number of bytes in a scanline, must >= (width * nBitCount / 8)
    @param bReversColors
    In case the indianess of pData is wrong, you could reverse colors
*/
BitmapEx CreateFromData( sal_uInt8 const *pData, sal_Int32 nWidth, sal_Int32 nHeight, sal_Int32 nStride, vcl::PixelFormat ePixelFormat)
BitmapEx CreateFromData( sal_uInt8 const *pData, sal_Int32 nWidth, sal_Int32 nHeight, sal_Int32 nStride, vcl::PixelFormat ePixelFormat, bool bReversColors )
{
    auto nBitCount = sal_uInt16(ePixelFormat);

@@ -150,11 +152,12 @@ BitmapEx CreateFromData( sal_uInt8 const *pData, sal_Int32 nWidth, sal_Int32 nHe
    {
        for( tools::Long y = 0; y < nHeight; ++y )
        {
            sal_uInt8 const *p = pData + y * nStride / 8;
            Scanline pScanline = pWrite->GetScanline(y);
            for (tools::Long x = 0; x < nWidth; ++x)
            {
                sal_uInt8 const *p = pData + y * nStride / 8;
                int bitIndex = (y * nStride) % 8;
                int bitIndex = (y * nStride + x) % 8;

                pWrite->SetPixelOnData(pScanline, x, BitmapColor((*p >> bitIndex) & 1));
            }
        }
@@ -167,7 +170,11 @@ BitmapEx CreateFromData( sal_uInt8 const *pData, sal_Int32 nWidth, sal_Int32 nHe
            Scanline pScanline = pWrite->GetScanline(y);
            for (tools::Long x = 0; x < nWidth; ++x)
            {
                BitmapColor col(p[0], p[1], p[2]);
                BitmapColor col;
                if ( bReversColors )
                    col = BitmapColor( p[2], p[1], p[0] );
                else
                    col = BitmapColor( p[0], p[1], p[2] );
                pWrite->SetPixelOnData(pScanline, x, col);
                p += nBitCount/8;
            }