include/vcl/filter/PngImageWriter.hxx | 26 ++++++------ vcl/qa/cppunit/png/PngFilterTest.cxx | 67 ++++++++++++++++++++++++++----- vcl/qa/cppunit/png/data/dummy.gif |binary vcl/source/filter/png/PngImageWriter.cxx | 59 +++++++++++++++++++++++++-- 4 files changed, 127 insertions(+), 25 deletions(-)
New commits: commit 089b101e5447aac42e6fc79345e60da3ec63893d Author: offtkp <[email protected]> AuthorDate: Sat Jul 16 12:34:47 2022 +0300 Commit: Tomaž Vajngerl <[email protected]> CommitDate: Tue Jul 19 10:00:10 2022 +0200 Add ms-gif PNG chunk export support in PngImageWriter Added export support for msOG chunks in the new PngImageWriter and unit test Change-Id: I258c9ca23e41c1d5ce01fc6711bc55c13636db17 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/137093 Tested-by: Jenkins Reviewed-by: Tomaž Vajngerl <[email protected]> diff --git a/include/vcl/filter/PngImageWriter.hxx b/include/vcl/filter/PngImageWriter.hxx index 667dd540e332..4fb11b1ca48a 100644 --- a/include/vcl/filter/PngImageWriter.hxx +++ b/include/vcl/filter/PngImageWriter.hxx @@ -13,11 +13,19 @@ #include <com/sun/star/uno/Sequence.hxx> #include <tools/stream.hxx> #include <vcl/bitmapex.hxx> +#include <vector> #pragma once namespace vcl { +// Similar to png_unknown_chunk +struct PngChunk +{ + std::array<uint8_t, 5> name; + std::vector<sal_uInt8> data; + size_t size; +}; class VCL_DLLPUBLIC PngImageWriter { SvStream& mrStream; @@ -25,23 +33,15 @@ class VCL_DLLPUBLIC PngImageWriter sal_Int32 mnCompressionLevel; bool mbInterlaced; + std::vector<PngChunk> maAdditionalChunks; public: PngImageWriter(SvStream& rStream); - virtual ~PngImageWriter() {} - - void setParameters(css::uno::Sequence<css::beans::PropertyValue> const& rParameters) - { - for (auto const& rValue : rParameters) - { - if (rValue.Name == "Compression") - rValue.Value >>= mnCompressionLevel; - else if (rValue.Name == "Interlaced") - rValue.Value >>= mbInterlaced; - } - } - bool write(BitmapEx& rBitmap); + virtual ~PngImageWriter() = default; + + void setParameters(css::uno::Sequence<css::beans::PropertyValue> const& rParameters); + bool write(const BitmapEx& rBitmap); }; } // namespace vcl diff --git a/vcl/qa/cppunit/png/PngFilterTest.cxx b/vcl/qa/cppunit/png/PngFilterTest.cxx index 64a99756aa89..33af620eb08a 100644 --- a/vcl/qa/cppunit/png/PngFilterTest.cxx +++ b/vcl/qa/cppunit/png/PngFilterTest.cxx @@ -1696,16 +1696,65 @@ void PngFilterTest::testPngSuite() void PngFilterTest::testMsGifInPng() { - Graphic aGraphic; - const OUString aURL(getFullUrl(u"ms-gif.png")); - SvFileStream aFileStream(aURL, StreamMode::READ); GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter(); - ErrCode aResult = rFilter.ImportGraphic(aGraphic, aURL, aFileStream); - CPPUNIT_ASSERT_EQUAL(ERRCODE_NONE, aResult); - CPPUNIT_ASSERT(aGraphic.IsGfxLink()); - // The image is technically a PNG, but it has an animated Gif as a chunk (Microsoft extension). - CPPUNIT_ASSERT_EQUAL(GfxLinkType::NativeGif, aGraphic.GetSharedGfxLink()->GetType()); - CPPUNIT_ASSERT(aGraphic.IsAnimated()); + { + Graphic aGraphic; + const OUString aURL(getFullUrl(u"ms-gif.png")); + SvFileStream aFileStream(aURL, StreamMode::READ); + ErrCode aResult = rFilter.ImportGraphic(aGraphic, aURL, aFileStream); + CPPUNIT_ASSERT_EQUAL(ERRCODE_NONE, aResult); + CPPUNIT_ASSERT(aGraphic.IsGfxLink()); + // The image is technically a PNG, but it has an animated Gif as a chunk (Microsoft extension). + CPPUNIT_ASSERT_EQUAL(GfxLinkType::NativeGif, aGraphic.GetSharedGfxLink()->GetType()); + CPPUNIT_ASSERT(aGraphic.IsAnimated()); + } + { + // Tests msOG chunk export support + const OUString aURL(getFullUrl(u"dummy.gif")); + SvFileStream aGIFStream(aURL, StreamMode::READ); + sal_uInt32 nGIFSize = aGIFStream.TellEnd(); + const char* const pHeader = "MSOFFICE9.0"; + auto nHeaderSize = strlen(pHeader); + uno::Sequence<sal_Int8> aGIFSequence(nHeaderSize + nGIFSize); + sal_Int8* pSequence = aGIFSequence.getArray(); + for (size_t i = 0; i < nHeaderSize; i++) + *pSequence++ = pHeader[i]; + aGIFStream.Seek(STREAM_SEEK_TO_BEGIN); + aGIFStream.ReadBytes(pSequence, nGIFSize); + // Create msOG chunk + beans::PropertyValue aChunkProperty, aFilterProperty; + aChunkProperty.Name = "msOG"; + aChunkProperty.Value <<= aGIFSequence; + uno::Sequence<beans::PropertyValue> aAdditionalChunkSequence{ aChunkProperty }; + aFilterProperty.Name = "AdditionalChunks"; + aFilterProperty.Value <<= aAdditionalChunkSequence; + uno::Sequence<beans::PropertyValue> aPNGParameters{ aFilterProperty }; + // Export the png with the chunk + OUString ext = u".png"; + utl::TempFile aTempFile(u"testPngExportMsGif", true, &ext); + if (!bKeepTemp) + aTempFile.EnableKillingFile(); + { + SvStream& rStream = *aTempFile.GetStream(StreamMode::WRITE); + BitmapEx aDummyBitmap(Size(8, 8), vcl::PixelFormat::N24_BPP); + vcl::PngImageWriter aPngWriter(rStream); + aPngWriter.setParameters(aPNGParameters); + bool bWriteSuccess = aPngWriter.write(aDummyBitmap); + CPPUNIT_ASSERT_EQUAL(true, bWriteSuccess); + aTempFile.CloseStream(); + } + { + SvStream& rStream = *aTempFile.GetStream(StreamMode::READ); + rStream.Seek(0); + // Import the png and check that it is a gif + Graphic aGraphic; + ErrCode aResult = rFilter.ImportGraphic(aGraphic, aTempFile.GetURL(), rStream); + CPPUNIT_ASSERT_EQUAL(ERRCODE_NONE, aResult); + CPPUNIT_ASSERT(aGraphic.IsGfxLink()); + CPPUNIT_ASSERT_EQUAL(GfxLinkType::NativeGif, aGraphic.GetSharedGfxLink()->GetType()); + CPPUNIT_ASSERT(aGraphic.IsAnimated()); + } + } } void PngFilterTest::testPngRoundtrip8BitGrey() diff --git a/vcl/qa/cppunit/png/data/dummy.gif b/vcl/qa/cppunit/png/data/dummy.gif new file mode 100644 index 000000000000..fd5c62dcdcb6 Binary files /dev/null and b/vcl/qa/cppunit/png/data/dummy.gif differ diff --git a/vcl/source/filter/png/PngImageWriter.cxx b/vcl/source/filter/png/PngImageWriter.cxx index 6a123e4eb547..7db4e4b6bc98 100644 --- a/vcl/source/filter/png/PngImageWriter.cxx +++ b/vcl/source/filter/png/PngImageWriter.cxx @@ -47,7 +47,8 @@ static void lclWriteStream(png_structp pPng, png_bytep pData, png_size_t pDataSi png_error(pPng, "Write Error"); } -static bool pngWrite(SvStream& rStream, BitmapEx& rBitmapEx, int nCompressionLevel) +static bool pngWrite(SvStream& rStream, const BitmapEx& rBitmapEx, int nCompressionLevel, + const std::vector<PngChunk>& aAdditionalChunks) { png_structp pPng = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); @@ -197,6 +198,14 @@ static bool pngWrite(SvStream& rStream, BitmapEx& rBitmapEx, int nCompressionLev } } + if (!aAdditionalChunks.empty()) + { + for (const auto& aChunk : aAdditionalChunks) + { + png_write_chunk(pPng, aChunk.name.data(), aChunk.data.data(), aChunk.size); + } + } + png_write_end(pPng, pInfo); png_destroy_write_struct(&pPng, &pInfo); @@ -204,6 +213,50 @@ static bool pngWrite(SvStream& rStream, BitmapEx& rBitmapEx, int nCompressionLev return true; } +void PngImageWriter::setParameters(css::uno::Sequence<css::beans::PropertyValue> const& rParameters) +{ + for (auto const& rValue : rParameters) + { + if (rValue.Name == "Compression") + rValue.Value >>= mnCompressionLevel; + else if (rValue.Name == "Interlaced") + rValue.Value >>= mbInterlaced; + else if (rValue.Name == "AdditionalChunks") + { + css::uno::Sequence<css::beans::PropertyValue> aAdditionalChunkSequence; + if (rValue.Value >>= aAdditionalChunkSequence) + { + for (const auto& rAdditionalChunk : std::as_const(aAdditionalChunkSequence)) + { + if (rAdditionalChunk.Name.getLength() == 4) + { + vcl::PngChunk aChunk; + for (sal_Int32 k = 0; k < 4; k++) + { + aChunk.name[k] = static_cast<sal_uInt8>(rAdditionalChunk.Name[k]); + } + aChunk.name[4] = '\0'; + + css::uno::Sequence<sal_Int8> aByteSeq; + if (rAdditionalChunk.Value >>= aByteSeq) + { + sal_uInt32 nChunkSize = aByteSeq.getLength(); + aChunk.size = nChunkSize; + if (nChunkSize) + { + const sal_Int8* pSource = aByteSeq.getConstArray(); + std::vector<sal_uInt8> aData(pSource, pSource + nChunkSize); + aChunk.data = std::move(aData); + maAdditionalChunks.push_back(aChunk); + } + } + } + } + } + } + } +} + PngImageWriter::PngImageWriter(SvStream& rStream) : mrStream(rStream) , mnCompressionLevel(6) @@ -211,9 +264,9 @@ PngImageWriter::PngImageWriter(SvStream& rStream) { } -bool PngImageWriter::write(BitmapEx& rBitmapEx) +bool PngImageWriter::write(const BitmapEx& rBitmapEx) { - return pngWrite(mrStream, rBitmapEx, mnCompressionLevel); + return pngWrite(mrStream, rBitmapEx, mnCompressionLevel, maAdditionalChunks); } } // namespace vcl
