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

Reply via email to