summaryrefslogtreecommitdiff
path: root/vcl
diff options
context:
space:
mode:
authorofftkp <parisoplop@gmail.com>2022-07-16 12:34:47 +0300
committerTomaž Vajngerl <quikee@gmail.com>2022-07-19 10:00:10 +0200
commit089b101e5447aac42e6fc79345e60da3ec63893d (patch)
tree23a1a96092b6b77c07d3e968a622a81f35ca2df3 /vcl
parent3d1032cf6b67f3f6fa539d1d42d681080517d38c (diff)
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 <quikee@gmail.com>
Diffstat (limited to 'vcl')
-rw-r--r--vcl/qa/cppunit/png/PngFilterTest.cxx67
-rw-r--r--vcl/qa/cppunit/png/data/dummy.gifbin0 -> 101 bytes
-rw-r--r--vcl/source/filter/png/PngImageWriter.cxx59
3 files changed, 114 insertions, 12 deletions
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
--- /dev/null
+++ b/vcl/qa/cppunit/png/data/dummy.gif
Binary files 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