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;
}