summaryrefslogtreecommitdiff
path: root/vcl
diff options
context:
space:
mode:
Diffstat (limited to 'vcl')
-rw-r--r--vcl/CppunitTest_vcl_filters_test.mk6
-rw-r--r--vcl/Executable_webpfuzzer.mk45
-rw-r--r--vcl/Library_vcl.mk3
-rw-r--r--vcl/Module_vcl.mk1
-rw-r--r--vcl/inc/filter/WebpReader.hxx28
-rw-r--r--vcl/inc/filter/WebpWriter.hxx29
-rw-r--r--vcl/inc/graphic/GraphicFormatDetector.hxx1
-rw-r--r--vcl/inc/graphic/UnoGraphicDescriptor.hxx1
-rw-r--r--vcl/qa/cppunit/GraphicDescriptorTest.cxx16
-rw-r--r--vcl/qa/cppunit/GraphicFormatDetectorTest.cxx17
-rw-r--r--vcl/qa/cppunit/GraphicTest.cxx2
-rw-r--r--vcl/qa/cppunit/data/TypeDetectionExample.webpbin0 -> 52 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/webp/alpha_lossless.webpbin0 -> 744 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/webp/alpha_lossy.webpbin0 -> 826 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/webp/fail/.gitignore0
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/webp/indeterminate/.gitignore0
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/webp/noalpha_lossless.webpbin0 -> 762 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/webp/noalpha_lossy.webpbin0 -> 782 bytes
-rw-r--r--vcl/qa/cppunit/graphicfilter/data/webp/pass/.gitignore0
-rw-r--r--vcl/qa/cppunit/graphicfilter/filters-test.cxx2
-rw-r--r--vcl/qa/cppunit/graphicfilter/filters-webp-test.cxx203
-rw-r--r--vcl/source/filter/FilterConfigCache.cxx6
-rw-r--r--vcl/source/filter/GraphicFormatDetector.cxx13
-rw-r--r--vcl/source/filter/graphicfilter.cxx74
-rw-r--r--vcl/source/filter/graphicfilter2.cxx33
-rw-r--r--vcl/source/filter/webp/reader.cxx319
-rw-r--r--vcl/source/filter/webp/writer.cxx206
-rw-r--r--vcl/source/gdi/gfxlink.cxx1
-rw-r--r--vcl/source/graphic/UnoGraphicDescriptor.cxx2
-rw-r--r--vcl/workben/fftester.cxx7
-rw-r--r--vcl/workben/webpfuzzer.cxx48
-rw-r--r--vcl/workben/webpfuzzer.options2
32 files changed, 1062 insertions, 3 deletions
diff --git a/vcl/CppunitTest_vcl_filters_test.mk b/vcl/CppunitTest_vcl_filters_test.mk
index 03a423233c9e..435a7fd04027 100644
--- a/vcl/CppunitTest_vcl_filters_test.mk
+++ b/vcl/CppunitTest_vcl_filters_test.mk
@@ -11,6 +11,12 @@ $(eval $(call gb_CppunitTest_CppunitTest,vcl_filters_test))
$(eval $(call gb_CppunitTest_add_exception_objects,vcl_filters_test, \
vcl/qa/cppunit/graphicfilter/filters-test \
+ vcl/qa/cppunit/graphicfilter/filters-webp-test \
+))
+
+$(eval $(call gb_CppunitTest_set_include,vcl_filters_test,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
))
ifeq ($(DISABLE_CVE_TESTS),TRUE)
diff --git a/vcl/Executable_webpfuzzer.mk b/vcl/Executable_webpfuzzer.mk
new file mode 100644
index 000000000000..3851fbe52cd1
--- /dev/null
+++ b/vcl/Executable_webpfuzzer.mk
@@ -0,0 +1,45 @@
+# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*-
+#
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+
+include $(SRCDIR)/vcl/commonfuzzer.mk
+
+$(eval $(call gb_Executable_Executable,webpfuzzer))
+
+$(eval $(call gb_Executable_use_api,webpfuzzer,\
+ offapi \
+ udkapi \
+))
+
+$(eval $(call gb_Executable_use_externals,webpfuzzer,\
+ $(fuzzer_externals) \
+))
+
+$(eval $(call gb_Executable_set_include,webpfuzzer,\
+ $$(INCLUDE) \
+ -I$(SRCDIR)/vcl/inc \
+))
+
+$(eval $(call gb_Executable_use_libraries,webpfuzzer,\
+ $(fuzzer_core_libraries) \
+))
+
+$(eval $(call gb_Executable_use_static_libraries,webpfuzzer,\
+ $(fuzzer_statics) \
+))
+
+$(eval $(call gb_Executable_add_exception_objects,webpfuzzer,\
+ vcl/workben/webpfuzzer \
+))
+
+$(eval $(call gb_Executable_add_libs,webpfuzzer,\
+ $(LIB_FUZZING_ENGINE) \
+))
+
+# vim: set noet sw=4 ts=4:
diff --git a/vcl/Library_vcl.mk b/vcl/Library_vcl.mk
index 44de3999b89f..9c6e7220330e 100644
--- a/vcl/Library_vcl.mk
+++ b/vcl/Library_vcl.mk
@@ -104,6 +104,7 @@ $(eval $(call gb_Library_use_externals,vcl,\
icu_headers \
icuuc \
lcms2 \
+ libwebp \
mdds_headers \
$(if $(filter SKIA,$(BUILD_TYPE)),skia) \
))
@@ -444,6 +445,8 @@ $(eval $(call gb_Library_add_exception_objects,vcl,\
vcl/source/filter/png/PngImageReader \
vcl/source/filter/png/pngread \
vcl/source/filter/png/pngwrite \
+ vcl/source/filter/webp/reader \
+ vcl/source/filter/webp/writer \
vcl/source/font/Feature \
vcl/source/font/FeatureCollector \
vcl/source/font/FeatureParser \
diff --git a/vcl/Module_vcl.mk b/vcl/Module_vcl.mk
index 3c9ef13c011b..cbaad39e8d3c 100644
--- a/vcl/Module_vcl.mk
+++ b/vcl/Module_vcl.mk
@@ -181,6 +181,7 @@ $(eval $(call gb_Module_add_targets,vcl,\
Executable_mtpfuzzer \
Executable_htmlfuzzer \
Executable_sftfuzzer \
+ Executable_webpfuzzer \
))
endif
diff --git a/vcl/inc/filter/WebpReader.hxx b/vcl/inc/filter/WebpReader.hxx
new file mode 100644
index 000000000000..fd8cc4894a26
--- /dev/null
+++ b/vcl/inc/filter/WebpReader.hxx
@@ -0,0 +1,28 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <vcl/graph.hxx>
+
+VCL_DLLPUBLIC bool ImportWebpGraphic(SvStream& rStream, Graphic& rGraphic);
+
+bool ReadWebpInfo(SvStream& rStream, Size& pixelSize, sal_uInt16& bitsPerPixel, bool& hasAlpha);
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/filter/WebpWriter.hxx b/vcl/inc/filter/WebpWriter.hxx
new file mode 100644
index 000000000000..d3b6431c63ff
--- /dev/null
+++ b/vcl/inc/filter/WebpWriter.hxx
@@ -0,0 +1,29 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#pragma once
+
+#include <tools/stream.hxx>
+#include <vcl/graph.hxx>
+#include <vcl/FilterConfigItem.hxx>
+
+VCL_DLLPUBLIC bool ExportWebpGraphic(SvStream& rStream, const Graphic& rGraphic,
+ FilterConfigItem* pFilterConfigItem);
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/inc/graphic/GraphicFormatDetector.hxx b/vcl/inc/graphic/GraphicFormatDetector.hxx
index e914ee64a3ca..3222fb712a4f 100644
--- a/vcl/inc/graphic/GraphicFormatDetector.hxx
+++ b/vcl/inc/graphic/GraphicFormatDetector.hxx
@@ -68,6 +68,7 @@ public:
bool checkTGA();
bool checkMOV();
bool checkPDF();
+ bool checkWEBP();
};
}
diff --git a/vcl/inc/graphic/UnoGraphicDescriptor.hxx b/vcl/inc/graphic/UnoGraphicDescriptor.hxx
index ad46952ce925..125a1f8b1d2b 100644
--- a/vcl/inc/graphic/UnoGraphicDescriptor.hxx
+++ b/vcl/inc/graphic/UnoGraphicDescriptor.hxx
@@ -51,6 +51,7 @@
#define MIMETYPE_EMF "image/x-emf"
#define MIMETYPE_SVG "image/svg+xml"
#define MIMETYPE_PDF "application/pdf"
+#define MIMETYPE_WEBP "image/webp"
#define MIMETYPE_VCLGRAPHIC "image/x-vclgraphic"
namespace comphelper { class PropertySetInfo; }
diff --git a/vcl/qa/cppunit/GraphicDescriptorTest.cxx b/vcl/qa/cppunit/GraphicDescriptorTest.cxx
index cb6c6d25d80a..9bbe10a5cea7 100644
--- a/vcl/qa/cppunit/GraphicDescriptorTest.cxx
+++ b/vcl/qa/cppunit/GraphicDescriptorTest.cxx
@@ -33,12 +33,14 @@ class GraphicDescriptorTest : public test::BootstrapFixtureBase
void testDetectJPG();
void testDetectGIF();
void testDetectBMP();
+ void testDetectWEBP();
CPPUNIT_TEST_SUITE(GraphicDescriptorTest);
CPPUNIT_TEST(testDetectPNG);
CPPUNIT_TEST(testDetectJPG);
CPPUNIT_TEST(testDetectGIF);
CPPUNIT_TEST(testDetectBMP);
+ CPPUNIT_TEST(testDetectWEBP);
CPPUNIT_TEST_SUITE_END();
};
@@ -122,6 +124,20 @@ void GraphicDescriptorTest::testDetectBMP()
CPPUNIT_ASSERT_EQUAL(MapUnit::MapMM, aGraphic.GetPrefMapMode().GetMapUnit());
}
+void GraphicDescriptorTest::testDetectWEBP()
+{
+ SvMemoryStream aStream;
+ createBitmapAndExportForType(aStream, u"webp");
+
+ GraphicDescriptor aDescriptor(aStream, nullptr);
+ aDescriptor.Detect(true);
+
+ CPPUNIT_ASSERT_EQUAL(GraphicFileFormat::WEBP, aDescriptor.GetFileFormat());
+
+ CPPUNIT_ASSERT_EQUAL(tools::Long(100), aDescriptor.GetSizePixel().Width());
+ CPPUNIT_ASSERT_EQUAL(tools::Long(100), aDescriptor.GetSizePixel().Height());
+}
+
} // namespace
CPPUNIT_TEST_SUITE_REGISTRATION(GraphicDescriptorTest);
diff --git a/vcl/qa/cppunit/GraphicFormatDetectorTest.cxx b/vcl/qa/cppunit/GraphicFormatDetectorTest.cxx
index 1ce516bf52b3..5f13b7d8d68e 100644
--- a/vcl/qa/cppunit/GraphicFormatDetectorTest.cxx
+++ b/vcl/qa/cppunit/GraphicFormatDetectorTest.cxx
@@ -43,6 +43,7 @@ class GraphicFormatDetectorTest : public test::BootstrapFixtureBase
void testDetectSVGZ();
void testDetectPDF();
void testDetectEPS();
+ void testDetectWEBP();
void testMatchArray();
void testCheckArrayForMatchingStrings();
@@ -63,6 +64,7 @@ class GraphicFormatDetectorTest : public test::BootstrapFixtureBase
CPPUNIT_TEST(testDetectSVGZ);
CPPUNIT_TEST(testDetectPDF);
CPPUNIT_TEST(testDetectEPS);
+ CPPUNIT_TEST(testDetectWEBP);
CPPUNIT_TEST(testMatchArray);
CPPUNIT_TEST(testCheckArrayForMatchingStrings);
CPPUNIT_TEST_SUITE_END();
@@ -308,6 +310,21 @@ void GraphicFormatDetectorTest::testDetectEPS()
CPPUNIT_ASSERT_EQUAL(OUString("EPS"), rFormatExtension);
}
+void GraphicFormatDetectorTest::testDetectWEBP()
+{
+ SvFileStream aFileStream(getFullUrl(u"TypeDetectionExample.webp"), StreamMode::READ);
+ vcl::GraphicFormatDetector aDetector(aFileStream, "WEBP");
+
+ CPPUNIT_ASSERT(aDetector.detect());
+ CPPUNIT_ASSERT(aDetector.checkWEBP());
+
+ aFileStream.Seek(aDetector.mnStreamPosition);
+
+ OUString rFormatExtension;
+ CPPUNIT_ASSERT(ImpPeekGraphicFormat(aFileStream, rFormatExtension, false));
+ CPPUNIT_ASSERT_EQUAL(OUString("WEBP"), rFormatExtension);
+}
+
void GraphicFormatDetectorTest::testMatchArray()
{
std::string aString("<?xml version=\"1.0\" standalone=\"no\"?>\n"
diff --git a/vcl/qa/cppunit/GraphicTest.cxx b/vcl/qa/cppunit/GraphicTest.cxx
index e6c114bf28f3..dd5bf03be84d 100644
--- a/vcl/qa/cppunit/GraphicTest.cxx
+++ b/vcl/qa/cppunit/GraphicTest.cxx
@@ -220,7 +220,7 @@ void GraphicTest::testUnloadedGraphic()
void GraphicTest::testUnloadedGraphicLoading()
{
- const OUString aFormats[] = { "png", "gif", "jpg" };
+ const OUString aFormats[] = { "png", "gif", "jpg", "webp" };
for (OUString const& sFormat : aFormats)
{
diff --git a/vcl/qa/cppunit/data/TypeDetectionExample.webp b/vcl/qa/cppunit/data/TypeDetectionExample.webp
new file mode 100644
index 000000000000..e85ae121637b
--- /dev/null
+++ b/vcl/qa/cppunit/data/TypeDetectionExample.webp
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/webp/alpha_lossless.webp b/vcl/qa/cppunit/graphicfilter/data/webp/alpha_lossless.webp
new file mode 100644
index 000000000000..abb67cc5f4b6
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/webp/alpha_lossless.webp
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/webp/alpha_lossy.webp b/vcl/qa/cppunit/graphicfilter/data/webp/alpha_lossy.webp
new file mode 100644
index 000000000000..c587b1bb22c0
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/webp/alpha_lossy.webp
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/webp/fail/.gitignore b/vcl/qa/cppunit/graphicfilter/data/webp/fail/.gitignore
new file mode 100644
index 000000000000..e69de29bb2d1
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/webp/fail/.gitignore
diff --git a/vcl/qa/cppunit/graphicfilter/data/webp/indeterminate/.gitignore b/vcl/qa/cppunit/graphicfilter/data/webp/indeterminate/.gitignore
new file mode 100644
index 000000000000..e69de29bb2d1
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/webp/indeterminate/.gitignore
diff --git a/vcl/qa/cppunit/graphicfilter/data/webp/noalpha_lossless.webp b/vcl/qa/cppunit/graphicfilter/data/webp/noalpha_lossless.webp
new file mode 100644
index 000000000000..b22ebc3b28bb
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/webp/noalpha_lossless.webp
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/webp/noalpha_lossy.webp b/vcl/qa/cppunit/graphicfilter/data/webp/noalpha_lossy.webp
new file mode 100644
index 000000000000..c118febb0e9a
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/webp/noalpha_lossy.webp
Binary files differ
diff --git a/vcl/qa/cppunit/graphicfilter/data/webp/pass/.gitignore b/vcl/qa/cppunit/graphicfilter/data/webp/pass/.gitignore
new file mode 100644
index 000000000000..e69de29bb2d1
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/data/webp/pass/.gitignore
diff --git a/vcl/qa/cppunit/graphicfilter/filters-test.cxx b/vcl/qa/cppunit/graphicfilter/filters-test.cxx
index fe7485e3944a..168ca916f641 100644
--- a/vcl/qa/cppunit/graphicfilter/filters-test.cxx
+++ b/vcl/qa/cppunit/graphicfilter/filters-test.cxx
@@ -144,6 +144,8 @@ void VclFiltersTest::testExportImport()
checkExportImport(u"png");
fprintf(stderr, "Check ExportImport BMP\n");
checkExportImport(u"bmp");
+ fprintf(stderr, "Check ExportImport WEBP\n");
+ checkExportImport(u"webp");
}
void VclFiltersTest::testCVEs()
diff --git a/vcl/qa/cppunit/graphicfilter/filters-webp-test.cxx b/vcl/qa/cppunit/graphicfilter/filters-webp-test.cxx
new file mode 100644
index 000000000000..8e404a686218
--- /dev/null
+++ b/vcl/qa/cppunit/graphicfilter/filters-webp-test.cxx
@@ -0,0 +1,203 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <unotest/filters-test.hxx>
+#include <test/bootstrapfixture.hxx>
+#include <vcl/FilterConfigItem.hxx>
+#include <bitmapwriteaccess.hxx>
+#include <tools/stream.hxx>
+#include <vcl/graph.hxx>
+#include <vcl/graphicfilter.hxx>
+#include <graphic/GraphicFormatDetector.hxx>
+#include <filter/WebpReader.hxx>
+#include <comphelper/propertyvalue.hxx>
+
+using namespace css;
+
+/* Implementation of Filters test */
+
+class WebpFilterTest : public test::FiltersTest, public test::BootstrapFixture
+{
+public:
+ WebpFilterTest()
+ : BootstrapFixture(true, false)
+ {
+ }
+
+ virtual bool load(const OUString&, const OUString& rURL, const OUString&, SfxFilterFlags,
+ SotClipboardFormatId, unsigned int) override;
+
+ /**
+ * Ensure CVEs remain unbroken
+ */
+ void testCVEs();
+
+ void testRoundtripLossless();
+ void testRoundtripLossy();
+ void testReadAlphaLossless();
+ void testReadAlphaLossy();
+ void testReadNoAlphaLossless();
+ void testReadNoAlphaLossy();
+
+ CPPUNIT_TEST_SUITE(WebpFilterTest);
+ CPPUNIT_TEST(testCVEs);
+ CPPUNIT_TEST(testRoundtripLossless);
+ CPPUNIT_TEST(testRoundtripLossy);
+ CPPUNIT_TEST(testReadAlphaLossless);
+ CPPUNIT_TEST(testReadAlphaLossy);
+ CPPUNIT_TEST(testReadNoAlphaLossless);
+ CPPUNIT_TEST(testReadNoAlphaLossy);
+ CPPUNIT_TEST_SUITE_END();
+
+private:
+ void testRoundtrip(bool lossy);
+ void testRead(bool lossy, bool alpha);
+};
+
+bool WebpFilterTest::load(const OUString&, const OUString& rURL, const OUString&, SfxFilterFlags,
+ SotClipboardFormatId, unsigned int)
+{
+ SvFileStream aFileStream(rURL, StreamMode::READ);
+ Graphic aGraphic;
+ return ImportWebpGraphic(aFileStream, aGraphic);
+}
+
+void WebpFilterTest::testCVEs()
+{
+#ifndef DISABLE_CVE_TESTS
+ testDir(OUString(), m_directories.getURLFromSrc(u"/vcl/qa/cppunit/graphicfilter/data/webp/"));
+#endif
+}
+
+void WebpFilterTest::testRoundtripLossless() { testRoundtrip(false); }
+
+void WebpFilterTest::testRoundtripLossy() { testRoundtrip(true); }
+
+void WebpFilterTest::testRoundtrip(bool lossy)
+{
+ // Do not use just 2x2, lossy saving would change colors.
+ Bitmap aBitmap(Size(20, 20), 24);
+ AlphaMask aAlpha(Size(20, 20));
+ {
+ BitmapScopedWriteAccess pAccess(aBitmap);
+ pAccess->SetFillColor(COL_WHITE);
+ pAccess->FillRect(tools::Rectangle(Point(0, 0), Size(10, 10)));
+ pAccess->SetFillColor(COL_BLACK);
+ pAccess->FillRect(tools::Rectangle(Point(10, 0), Size(10, 10)));
+ pAccess->SetFillColor(COL_LIGHTRED);
+ pAccess->FillRect(tools::Rectangle(Point(0, 10), Size(10, 10)));
+ pAccess->SetFillColor(COL_BLUE);
+ pAccess->FillRect(tools::Rectangle(Point(10, 10), Size(10, 10)));
+ AlphaScopedWriteAccess pAccessAlpha(aAlpha);
+ pAccessAlpha->SetFillColor(BitmapColor(0)); // opaque
+ pAccessAlpha->FillRect(tools::Rectangle(Point(0, 0), Size(10, 10)));
+ pAccessAlpha->FillRect(tools::Rectangle(Point(10, 0), Size(10, 10)));
+ pAccessAlpha->FillRect(tools::Rectangle(Point(0, 10), Size(10, 10)));
+ pAccessAlpha->SetFillColor(BitmapColor(64, 64, 64));
+ pAccessAlpha->FillRect(tools::Rectangle(Point(10, 10), Size(10, 10)));
+ }
+ BitmapEx aBitmapEx(aBitmap, aAlpha);
+
+ SvMemoryStream aStream;
+ GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter();
+ sal_uInt16 nFilterFormat = rFilter.GetExportFormatNumberForShortName(u"webp");
+ css::uno::Sequence<css::beans::PropertyValue> aFilterData{
+ comphelper::makePropertyValue("Lossless", !lossy),
+ comphelper::makePropertyValue("Quality", sal_Int32(100))
+ };
+ rFilter.ExportGraphic(Graphic(aBitmapEx), "none", aStream, nFilterFormat, &aFilterData);
+ aStream.Seek(STREAM_SEEK_TO_BEGIN);
+
+ Graphic aGraphic;
+ ErrCode bResult = rFilter.ImportGraphic(aGraphic, "none", aStream);
+ CPPUNIT_ASSERT_EQUAL(ERRCODE_NONE, bResult);
+ CPPUNIT_ASSERT_EQUAL(GfxLinkType::NativeWebp, aGraphic.GetGfxLink().GetType());
+ BitmapEx aResultBitmap = aGraphic.GetBitmapEx();
+ CPPUNIT_ASSERT_EQUAL(Size(20, 20), aResultBitmap.GetSizePixel());
+ CPPUNIT_ASSERT(aResultBitmap.IsAlpha());
+
+ {
+ Bitmap tmpBitmap = aResultBitmap.GetBitmap();
+ Bitmap::ScopedReadAccess pAccess(tmpBitmap);
+ // Note that x,y are swapped.
+ CPPUNIT_ASSERT_EQUAL(COL_WHITE, Color(pAccess->GetPixel(0, 0)));
+ CPPUNIT_ASSERT_EQUAL(COL_BLACK, Color(pAccess->GetPixel(0, 19)));
+ if (lossy)
+ {
+ CPPUNIT_ASSERT_LESS(sal_uInt16(3),
+ pAccess->GetPixel(19, 0).GetColorError(COL_LIGHTRED));
+ CPPUNIT_ASSERT_LESS(sal_uInt16(3), pAccess->GetPixel(19, 19).GetColorError(COL_BLUE));
+ }
+ else
+ {
+ CPPUNIT_ASSERT_EQUAL(COL_LIGHTRED, Color(pAccess->GetPixel(19, 0)));
+ CPPUNIT_ASSERT_EQUAL(COL_BLUE, Color(pAccess->GetPixel(19, 19)));
+ }
+ AlphaMask tmpAlpha = aResultBitmap.GetAlpha();
+ AlphaMask::ScopedReadAccess pAccessAlpha(tmpAlpha);
+ CPPUNIT_ASSERT_EQUAL(sal_uInt8(0), pAccessAlpha->GetPixelIndex(0, 0));
+ CPPUNIT_ASSERT_EQUAL(sal_uInt8(0), pAccessAlpha->GetPixelIndex(0, 19));
+ CPPUNIT_ASSERT_EQUAL(sal_uInt8(0), pAccessAlpha->GetPixelIndex(19, 0));
+ CPPUNIT_ASSERT_EQUAL(sal_uInt8(64), pAccessAlpha->GetPixelIndex(19, 19));
+ }
+
+ aStream.Seek(STREAM_SEEK_TO_BEGIN);
+ vcl::GraphicFormatDetector aDetector(aStream, "");
+
+ CPPUNIT_ASSERT_EQUAL(true, aDetector.detect());
+ CPPUNIT_ASSERT_EQUAL(true, aDetector.checkWEBP());
+ CPPUNIT_ASSERT_EQUAL(OUString(u"WEBP"), aDetector.msDetectedFormat);
+}
+
+void WebpFilterTest::testReadAlphaLossless() { testRead(false, true); }
+
+void WebpFilterTest::testReadAlphaLossy() { testRead(true, true); }
+
+void WebpFilterTest::testReadNoAlphaLossless() { testRead(false, false); }
+
+void WebpFilterTest::testReadNoAlphaLossy() { testRead(true, false); }
+
+void WebpFilterTest::testRead(bool lossy, bool alpha)
+{
+ // Read a file created in GIMP and check it's read correctly.
+ OUString file = m_directories.getURLFromSrc(u"/vcl/qa/cppunit/graphicfilter/data/webp/")
+ + (alpha ? u"alpha" : u"noalpha") + "_" + (lossy ? u"lossy" : u"lossless")
+ + ".webp";
+ SvFileStream aFileStream(file, StreamMode::READ);
+ Graphic aGraphic;
+ GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter();
+ ErrCode bResult = rFilter.ImportGraphic(aGraphic, "none", aFileStream);
+ CPPUNIT_ASSERT_EQUAL(ERRCODE_NONE, bResult);
+ CPPUNIT_ASSERT_EQUAL(GfxLinkType::NativeWebp, aGraphic.GetGfxLink().GetType());
+ BitmapEx aResultBitmap = aGraphic.GetBitmapEx();
+ CPPUNIT_ASSERT_EQUAL(Size(10, 10), aResultBitmap.GetSizePixel());
+ CPPUNIT_ASSERT_EQUAL(alpha, aResultBitmap.IsAlpha());
+
+ {
+ Bitmap tmpBitmap = aResultBitmap.GetBitmap();
+ Bitmap::ScopedReadAccess pAccess(tmpBitmap);
+ // Note that x,y are swapped.
+ if (lossy)
+ CPPUNIT_ASSERT_LESS(sal_uInt16(2), pAccess->GetPixel(0, 0).GetColorError(COL_LIGHTRED));
+ else
+ CPPUNIT_ASSERT_EQUAL(COL_LIGHTRED, Color(pAccess->GetPixel(0, 0)));
+ CPPUNIT_ASSERT_EQUAL(COL_LIGHTBLUE, Color(pAccess->GetPixel(9, 9)));
+ if (alpha)
+ {
+ AlphaMask tmpAlpha = aResultBitmap.GetAlpha();
+ AlphaMask::ScopedReadAccess pAccessAlpha(tmpAlpha);
+ CPPUNIT_ASSERT_EQUAL(sal_uInt8(0), pAccessAlpha->GetPixelIndex(0, 0));
+ CPPUNIT_ASSERT_EQUAL(sal_uInt8(255), pAccessAlpha->GetPixelIndex(0, 9));
+ }
+ }
+}
+
+CPPUNIT_TEST_SUITE_REGISTRATION(WebpFilterTest);
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/FilterConfigCache.cxx b/vcl/source/filter/FilterConfigCache.cxx
index 478756ff17f0..acd3086e5a5d 100644
--- a/vcl/source/filter/FilterConfigCache.cxx
+++ b/vcl/source/filter/FilterConfigCache.cxx
@@ -41,8 +41,8 @@ using namespace ::com::sun::star::configuration ;
const char* FilterConfigCache::FilterConfigCacheEntry::InternalPixelFilterNameList[] =
{
- IMP_BMP, IMP_GIF, IMP_PNG,IMP_JPEG, IMP_XBM, IMP_XPM,
- EXP_BMP, EXP_JPEG, EXP_PNG, IMP_MOV, nullptr
+ IMP_BMP, IMP_GIF, IMP_PNG,IMP_JPEG, IMP_XBM, IMP_XPM, IMP_WEBP,
+ EXP_BMP, EXP_JPEG, EXP_PNG, IMP_MOV, EXP_WEBP, nullptr
};
const char* FilterConfigCache::FilterConfigCacheEntry::InternalVectorFilterNameList[] =
@@ -255,6 +255,8 @@ const char* FilterConfigCache::InternalFilterListForSvxLight[] =
"xpm","2","exp",
"svg","1","SVISVG",
"svg","2","SVESVG",
+ "webp","1","SVIWEBP",
+ "webp","2","SVEWEBP",
nullptr
};
diff --git a/vcl/source/filter/GraphicFormatDetector.cxx b/vcl/source/filter/GraphicFormatDetector.cxx
index 56f7a9f2d006..162125f3fe97 100644
--- a/vcl/source/filter/GraphicFormatDetector.cxx
+++ b/vcl/source/filter/GraphicFormatDetector.cxx
@@ -28,6 +28,7 @@
namespace vcl
{
+
namespace
{
bool isPCT(SvStream& rStream, sal_uLong nStreamPos, sal_uLong nStreamLen)
@@ -545,6 +546,18 @@ bool GraphicFormatDetector::checkPDF()
return false;
}
+bool GraphicFormatDetector::checkWEBP()
+{
+ if (maFirstBytes[0] == 'R' && maFirstBytes[1] == 'I' && maFirstBytes[2] == 'F'
+ && maFirstBytes[3] == 'F' && maFirstBytes[8] == 'W' && maFirstBytes[9] == 'E'
+ && maFirstBytes[10] == 'B' && maFirstBytes[11] == 'P')
+ {
+ msDetectedFormat = "WEBP";
+ return true;
+ }
+ return false;
+}
+
} // vcl namespace
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/graphicfilter.cxx b/vcl/source/filter/graphicfilter.cxx
index 4a606d7d1ebb..f57475eda91c 100644
--- a/vcl/source/filter/graphicfilter.cxx
+++ b/vcl/source/filter/graphicfilter.cxx
@@ -47,6 +47,8 @@
#include "jpeg/jpeg.hxx"
#include "ixbm/xbmread.hxx"
#include "ixpm/xpmread.hxx"
+#include <filter/WebpReader.hxx>
+#include <filter/WebpWriter.hxx>
#include <osl/module.hxx>
#include <com/sun/star/uno/Reference.h>
#include <com/sun/star/awt/Size.hpp>
@@ -65,6 +67,7 @@
#include <vector>
#include <memory>
#include <string_view>
+#include <o3tl/string_view.hxx>
#include "FilterConfigCache.hxx"
#include "graphicfilter_internal.hxx"
@@ -74,6 +77,41 @@
#define PMGCHUNG_msOG 0x6d734f47 // Microsoft Office Animated GIF
+// Support for GfxLinkType::NativeWebp is so far disabled,
+// as enabling it would write .webp images e.g. to .odt documents,
+// making those images unreadable for older readers. So for now
+// disable the support so that .webp images will be written out as .png,
+// and somewhen later enable the support unconditionally.
+
+namespace
+{
+template <typename charT, typename traits = std::char_traits<charT>>
+constexpr bool starts_with(std::basic_string_view<charT, traits> sv,
+ std::basic_string_view<charT, traits> x) noexcept
+{
+ return sv.substr(0, x.size()) == x;
+}
+template <typename charT, typename traits = std::char_traits<charT>>
+constexpr bool starts_with(std::basic_string_view<charT, traits> sv, charT const* x)
+{
+ return starts_with(sv, std::basic_string_view<charT, traits>(x));
+}
+
+}
+
+static bool supportNativeWebp()
+{
+ const char* const testname = getenv("LO_TESTNAME");
+ if(testname == nullptr)
+ return false;
+ // Enable support only for those unittests that test it.
+ if( std::string_view("_anonymous_namespace___GraphicTest__testUnloadedGraphicLoading_") == testname
+ || std::string_view("VclFiltersTest__testExportImport_") == testname
+ || starts_with(std::string_view(testname), "WebpFilterTest__"))
+ return true;
+ return false;
+}
+
typedef ::std::vector< GraphicFilter* > FilterList_impl;
static FilterList_impl* pFilterHdlList = nullptr;
@@ -444,6 +482,16 @@ bool ImpPeekGraphicFormat( SvStream& rStream, OUString& rFormatExtension, bool b
}
}
+ if (!bTest || rFormatExtension.startsWith("WEBP"))
+ {
+ bSomethingTested = true;
+ if (aDetector.checkWEBP())
+ {
+ rFormatExtension = aDetector.msDetectedFormat;
+ return true;
+ }
+ }
+
return bTest && !bSomethingTested;
}
@@ -1325,6 +1373,13 @@ Graphic GraphicFilter::ImportUnloadedGraphic(SvStream& rIStream, sal_uInt64 size
{
eLinkType = GfxLinkType::NativePdf;
}
+ else if (aFilterName.equalsIgnoreAsciiCase(IMP_WEBP))
+ {
+ if(supportNativeWebp())
+ eLinkType = GfxLinkType::NativeWebp;
+ else
+ nStatus = ERRCODE_GRFILTER_FILTERERROR;
+ }
else
{
nStatus = ERRCODE_GRFILTER_FILTERERROR;
@@ -1726,6 +1781,16 @@ ErrCode GraphicFilter::ImportGraphic( Graphic& rGraphic, const OUString& rPath,
else
nStatus = ERRCODE_GRFILTER_FILTERERROR;
}
+ else if (aFilterName.equalsIgnoreAsciiCase(IMP_WEBP))
+ {
+ if (ImportWebpGraphic(rIStream, rGraphic))
+ {
+ if(supportNativeWebp())
+ eLinkType = GfxLinkType::NativeWebp;
+ }
+ else
+ nStatus = ERRCODE_GRFILTER_FILTERERROR;
+ }
else
nStatus = ERRCODE_GRFILTER_FILTERERROR;
}
@@ -2150,6 +2215,14 @@ ErrCode GraphicFilter::ExportGraphic( const Graphic& rGraphic, const OUString& r
}
}
}
+ else if (aFilterName.equalsIgnoreAsciiCase(EXP_WEBP))
+ {
+ if (!ExportWebpGraphic(rOStm, aGraphic, &aConfigItem))
+ nStatus = ERRCODE_GRFILTER_FORMATERROR;
+
+ if( rOStm.GetError() )
+ nStatus = ERRCODE_GRFILTER_IOERROR;
+ }
else
nStatus = ERRCODE_GRFILTER_FILTERERROR;
}
@@ -2230,6 +2303,7 @@ IMPL_LINK( GraphicFilter, FilterCallback, ConvertData&, rData, bool )
case ConvertDataFormat::WMF: aShortName = WMF_SHORTNAME; break;
case ConvertDataFormat::EMF: aShortName = EMF_SHORTNAME; break;
case ConvertDataFormat::SVG: aShortName = SVG_SHORTNAME; break;
+ case ConvertDataFormat::WEBP: aShortName = WEBP_SHORTNAME; break;
default:
break;
diff --git a/vcl/source/filter/graphicfilter2.cxx b/vcl/source/filter/graphicfilter2.cxx
index ebebf829a33f..dd13e8123340 100644
--- a/vcl/source/filter/graphicfilter2.cxx
+++ b/vcl/source/filter/graphicfilter2.cxx
@@ -25,6 +25,7 @@
#include <vcl/outdev.hxx>
#include <vcl/graphicfilter.hxx>
#include <unotools/ucbstreamhelper.hxx>
+#include <filter/WebpReader.hxx>
#include "graphicfilter_internal.hxx"
#define DATA_SIZE 640
@@ -87,6 +88,7 @@ bool GraphicDescriptor::Detect( bool bExtendedInfo )
else if ( ImpDetectPSD( rStm, bExtendedInfo ) ) bRet = true;
else if ( ImpDetectEPS( rStm, bExtendedInfo ) ) bRet = true;
else if ( ImpDetectPCD( rStm, bExtendedInfo ) ) bRet = true;
+ else if ( ImpDetectWEBP( rStm, bExtendedInfo ) ) bRet = true;
rStm.SetEndian( nOldFormat );
}
@@ -1143,6 +1145,36 @@ bool GraphicDescriptor::ImpDetectSVG( SvStream& /*rStm*/, bool /*bExtendedInfo*/
return bRet;
}
+bool GraphicDescriptor::ImpDetectWEBP( SvStream& rStm, bool bExtendedInfo )
+{
+ sal_uInt32 nTemp32 = 0;
+ bool bRet = false;
+
+ sal_Int32 nStmPos = rStm.Tell();
+ rStm.SetEndian( SvStreamEndian::BIG );
+ rStm.ReadUInt32( nTemp32 );
+
+ if ( nTemp32 == 0x52494646 )
+ {
+ rStm.ReadUInt32( nTemp32 ); // skip
+ rStm.ReadUInt32( nTemp32 );
+ if ( nTemp32 == 0x57454250 )
+ {
+ nFormat = GraphicFileFormat::WEBP;
+ bRet = true;
+
+ if ( bExtendedInfo )
+ {
+ rStm.Seek(nStmPos);
+ ReadWebpInfo(rStm, aPixSize, nBitsPerPixel, bIsAlpha );
+ bIsTransparent = bIsAlpha;
+ }
+ }
+ }
+ rStm.Seek( nStmPos );
+ return bRet;
+}
+
OUString GraphicDescriptor::GetImportFormatShortName( GraphicFileFormat nFormat )
{
const char *pKeyName = nullptr;
@@ -1172,6 +1204,7 @@ OUString GraphicDescriptor::GetImportFormatShortName( GraphicFileFormat nFormat
case GraphicFileFormat::WMF : pKeyName = "wmf"; break;
case GraphicFileFormat::EMF : pKeyName = "emf"; break;
case GraphicFileFormat::SVG : pKeyName = "svg"; break;
+ case GraphicFileFormat::WEBP : pKeyName = "webp"; break;
default: assert(false);
}
diff --git a/vcl/source/filter/webp/reader.cxx b/vcl/source/filter/webp/reader.cxx
new file mode 100644
index 000000000000..90a9278e16dc
--- /dev/null
+++ b/vcl/source/filter/webp/reader.cxx
@@ -0,0 +1,319 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <memory>
+#include <vcl/graph.hxx>
+#include <tools/stream.hxx>
+#include <filter/WebpReader.hxx>
+#include <bitmapwriteaccess.hxx>
+#include <salinst.hxx>
+#include <sal/log.hxx>
+#include <unotools/configmgr.hxx>
+#include <svdata.hxx>
+
+#include <webp/decode.h>
+
+static bool readWebpInfo(SvStream& stream, std::vector<uint8_t>& data,
+ WebPBitstreamFeatures& features)
+{
+ for (;;)
+ {
+ // Read 4096 (more) bytes.
+ size_t lastSize = data.size();
+ data.resize(data.size() + 4096);
+ sal_Size nBytesRead = stream.ReadBytes(data.data() + lastSize, 4096);
+ if (nBytesRead <= 0)
+ return false;
+ data.resize(lastSize + nBytesRead);
+ int status = WebPGetFeatures(data.data(), data.size(), &features);
+ if (status == VP8_STATUS_OK)
+ break;
+ if (status == VP8_STATUS_NOT_ENOUGH_DATA)
+ continue; // Try again with 4096 more bytes read.
+ return false;
+ }
+ return true;
+}
+
+static bool readWebp(SvStream& stream, Graphic& graphic)
+{
+ WebPDecoderConfig config;
+ if (!WebPInitDecoderConfig(&config))
+ {
+ SAL_WARN("vcl.filter.webp", "WebPInitDecoderConfig() failed");
+ return false;
+ }
+ // This unique_ptr is here just to ensure WebPFreeDecBuffer() is called at the end,
+ // it doesn't actually own the data as such.
+ std::unique_ptr<WebPDecBuffer, decltype(&WebPFreeDecBuffer)> freeBuffer(&config.output,
+ WebPFreeDecBuffer);
+ std::vector<uint8_t> data;
+ if (!readWebpInfo(stream, data, config.input))
+ return false;
+ // Here various parts of 'config' can be altered if wanted.
+ const int& width = config.input.width;
+ const int& height = config.input.height;
+ const int& has_alpha = config.input.has_alpha;
+
+ if (width > SAL_MAX_INT32 / 8 || height > SAL_MAX_INT32 / 8)
+ return false; // avoid overflows later
+
+ const bool bFuzzing = utl::ConfigManager::IsFuzzing();
+ auto pBackendCapabilities = ImplGetSVData()->mpDefInst->GetBackendCapabilities();
+ bool bSupportsBitmap32 = bFuzzing || pBackendCapabilities->mbSupportsBitmap32;
+
+ Bitmap bitmap;
+ AlphaMask bitmapAlpha;
+ if (bSupportsBitmap32 && has_alpha)
+ {
+ bitmap = Bitmap(Size(width, height), 32);
+ }
+ else
+ {
+ bitmap = Bitmap(Size(width, height), 24);
+ if (has_alpha)
+ bitmapAlpha = AlphaMask(Size(width, height));
+ }
+
+ BitmapScopedWriteAccess access(bitmap);
+ // If data cannot be read directly into the bitmap, read data first to this buffer and then convert.
+ std::vector<uint8_t> tmpRgbaData;
+ enum class PixelMode
+ {
+ DirectRead, // read data directly to the bitmap
+ Split, // read to tmp buffer and split to rgb and alpha
+ SetPixel // read to tmp buffer and use setPixel()
+ };
+ PixelMode pixelMode = PixelMode::SetPixel;
+
+ config.output.width = width;
+ config.output.height = height;
+ config.output.is_external_memory = 1;
+ if (bSupportsBitmap32 && has_alpha)
+ {
+ switch (RemoveScanline(access->GetScanlineFormat()))
+ {
+ // Our bitmap32 code expects premultiplied.
+ case ScanlineFormat::N32BitTcRgba:
+ config.output.colorspace = MODE_rgbA;
+ pixelMode = PixelMode::DirectRead;
+ break;
+ case ScanlineFormat::N32BitTcBgra:
+ config.output.colorspace = MODE_bgrA;
+ pixelMode = PixelMode::DirectRead;
+ break;
+ case ScanlineFormat::N32BitTcArgb:
+ config.output.colorspace = MODE_Argb;
+ pixelMode = PixelMode::DirectRead;
+ break;
+ default:
+ config.output.colorspace = MODE_RGBA;
+ pixelMode = PixelMode::SetPixel;
+ break;
+ }
+ }
+ else
+ {
+ if (has_alpha)
+ {
+ switch (RemoveScanline(access->GetScanlineFormat()))
+ {
+ case ScanlineFormat::N24BitTcRgb:
+ config.output.colorspace = MODE_RGBA;
+ pixelMode = PixelMode::Split;
+ break;
+ case ScanlineFormat::N24BitTcBgr:
+ config.output.colorspace = MODE_BGRA;
+ pixelMode = PixelMode::Split;
+ break;
+ default:
+ config.output.colorspace = MODE_RGBA;
+ pixelMode = PixelMode::SetPixel;
+ break;
+ }
+ }
+ else
+ {
+ switch (RemoveScanline(access->GetScanlineFormat()))
+ {
+ case ScanlineFormat::N24BitTcRgb:
+ config.output.colorspace = MODE_RGB;
+ pixelMode = PixelMode::DirectRead;
+ break;
+ case ScanlineFormat::N24BitTcBgr:
+ config.output.colorspace = MODE_BGR;
+ pixelMode = PixelMode::DirectRead;
+ break;
+ default:
+ config.output.colorspace = MODE_RGBA;
+ pixelMode = PixelMode::SetPixel;
+ break;
+ }
+ }
+ }
+ if (pixelMode == PixelMode::DirectRead)
+ {
+ config.output.u.RGBA.rgba = access->GetBuffer();
+ config.output.u.RGBA.stride = access->GetScanlineSize();
+ config.output.u.RGBA.size = access->GetScanlineSize() * access->Height();
+ }
+ else
+ {
+ tmpRgbaData.resize(width * height * 4);
+ config.output.u.RGBA.rgba = tmpRgbaData.data();
+ config.output.u.RGBA.stride = width * 4;
+ config.output.u.RGBA.size = tmpRgbaData.size();
+ }
+
+ std::unique_ptr<WebPIDecoder, decltype(&WebPIDelete)> decoder(WebPIDecode(nullptr, 0, &config),
+ WebPIDelete);
+
+ bool success = true;
+ for (;;)
+ {
+ // During first iteration, use data read while reading the header.
+ int status = WebPIAppend(decoder.get(), data.data(), data.size());
+ if (status == VP8_STATUS_OK)
+ break;
+ if (status != VP8_STATUS_SUSPENDED)
+ {
+ // An error, still try to return at least a partially read bitmap,
+ // even if returning an error flag.
+ success = false;
+ break;
+ }
+ // If more data is needed, reading 4096 bytes more and repeat.
+ data.resize(4096);
+ sal_Size nBytesRead = stream.ReadBytes(data.data(), 4096);
+ if (nBytesRead <= 0)
+ {
+ // Truncated file, again try to return at least something.
+ success = false;
+ break;
+ }
+ data.resize(nBytesRead);
+ }
+
+ switch (pixelMode)
+ {
+ case PixelMode::DirectRead:
+ {
+ // Adjust for IsBottomUp() if necessary.
+ if (access->IsBottomUp())
+ {
+ std::vector<char> tmp;
+ const sal_uInt32 lineSize = access->GetScanlineSize();
+ tmp.resize(lineSize);
+ for (tools::Long y = 0; y < access->Height() / 2; ++y)
+ {
+ tools::Long otherY = access->Height() - 1 - y;
+ memcpy(tmp.data(), access->GetScanline(y), lineSize);
+ memcpy(access->GetScanline(y), access->GetScanline(otherY), lineSize);
+ memcpy(access->GetScanline(otherY), tmp.data(), lineSize);
+ }
+ }
+ break;
+ }
+ case PixelMode::Split:
+ {
+ // Split to normal and alpha bitmaps.
+ AlphaScopedWriteAccess accessAlpha(bitmapAlpha);
+ for (tools::Long y = 0; y < access->Height(); ++y)
+ {
+ const unsigned char* src = tmpRgbaData.data() + width * 4 * y;
+ unsigned char* dstB = access->GetScanline(y);
+ unsigned char* dstA = accessAlpha->GetScanline(y);
+ for (tools::Long x = 0; x < access->Width(); ++x)
+ {
+ memcpy(dstB, src, 3);
+ *dstA = 255 - *(src + 3);
+ src += 4;
+ dstB += 3;
+ dstA += 1;
+ }
+ }
+ break;
+ }
+ case PixelMode::SetPixel:
+ {
+ for (tools::Long y = 0; y < access->Height(); ++y)
+ {
+ const unsigned char* src = tmpRgbaData.data() + width * 4 * y;
+ for (tools::Long x = 0; x < access->Width(); ++x)
+ {
+ sal_uInt8 r = src[0];
+ sal_uInt8 g = src[1];
+ sal_uInt8 b = src[2];
+ sal_uInt8 a = src[3];
+ access->SetPixel(y, x, Color(255 - a, r, g, b));
+ src += 4;
+ }
+ }
+ if (!bitmapAlpha.IsEmpty())
+ {
+ AlphaScopedWriteAccess accessAlpha(bitmapAlpha);
+ for (tools::Long y = 0; y < accessAlpha->Height(); ++y)
+ {
+ const unsigned char* src = tmpRgbaData.data() + width * 4 * y;
+ for (tools::Long x = 0; x < accessAlpha->Width(); ++x)
+ {
+ sal_uInt8 a = src[3];
+ accessAlpha->SetPixelIndex(y, x, 255 - a);
+ src += 4;
+ }
+ }
+ }
+ break;
+ }
+ }
+
+ access.reset(); // Flush BitmapScopedWriteAccess.
+ if (bSupportsBitmap32 && has_alpha)
+ graphic = BitmapEx(bitmap);
+ else
+ {
+ if (has_alpha)
+ graphic = BitmapEx(bitmap, bitmapAlpha);
+ else
+ graphic = BitmapEx(bitmap);
+ }
+ return success;
+}
+
+bool ImportWebpGraphic(SvStream& rStream, Graphic& rGraphic)
+{
+ bool bRetValue = readWebp(rStream, rGraphic);
+ if (!bRetValue)
+ rStream.SetError(SVSTREAM_FILEFORMAT_ERROR);
+ return bRetValue;
+}
+
+bool ReadWebpInfo(SvStream& stream, Size& pixelSize, sal_uInt16& bitsPerPixel, bool& hasAlpha)
+{
+ std::vector<uint8_t> data;
+ WebPBitstreamFeatures features;
+ if (!readWebpInfo(stream, data, features))
+ return false;
+ pixelSize = Size(features.width, features.height);
+ bitsPerPixel = features.has_alpha ? 32 : 24;
+ hasAlpha = features.has_alpha;
+ return true;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/filter/webp/writer.cxx b/vcl/source/filter/webp/writer.cxx
new file mode 100644
index 000000000000..de6998caa591
--- /dev/null
+++ b/vcl/source/filter/webp/writer.cxx
@@ -0,0 +1,206 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <memory>
+#include <vcl/graph.hxx>
+#include <tools/stream.hxx>
+#include <filter/WebpWriter.hxx>
+#include <vcl/bitmapaccess.hxx>
+#include <sal/log.hxx>
+
+#include <webp/encode.h>
+
+static int writerFunction(const uint8_t* data, size_t size, const WebPPicture* picture)
+{
+ SvStream* stream = static_cast<SvStream*>(picture->custom_ptr);
+ return stream->WriteBytes(data, size) == size ? 1 : 0;
+}
+
+static WebPPreset presetToValue(const OUString& preset)
+{
+ if (preset.equalsIgnoreAsciiCase("picture"))
+ return WEBP_PRESET_PICTURE;
+ if (preset.equalsIgnoreAsciiCase("photo"))
+ return WEBP_PRESET_PHOTO;
+ if (preset.equalsIgnoreAsciiCase("drawing"))
+ return WEBP_PRESET_DRAWING;
+ if (preset.equalsIgnoreAsciiCase("icon"))
+ return WEBP_PRESET_ICON;
+ if (preset.equalsIgnoreAsciiCase("text"))
+ return WEBP_PRESET_TEXT;
+ return WEBP_PRESET_DEFAULT;
+}
+
+static bool writeWebp(SvStream& rStream, const BitmapEx& bitmapEx, bool lossless,
+ const OUString& preset, int quality)
+{
+ WebPConfig config;
+ if (!WebPConfigInit(&config))
+ {
+ SAL_WARN("vcl.filter.webp", "WebPConfigInit() failed");
+ return false;
+ }
+ if (lossless)
+ {
+ if (!WebPConfigLosslessPreset(&config, 6))
+ {
+ SAL_WARN("vcl.filter.webp", "WebPConfigLosslessPreset() failed");
+ return false;
+ }
+ }
+ else
+ {
+ if (!WebPConfigPreset(&config, presetToValue(preset), quality))
+ {
+ SAL_WARN("vcl.filter.webp", "WebPConfigPreset() failed");
+ return false;
+ }
+ }
+ // Here various parts of 'config' can be altered if wanted.
+ assert(WebPValidateConfig(&config));
+
+ const int width = bitmapEx.GetSizePixel().Width();
+ const int height = bitmapEx.GetSizePixel().Height();
+
+ WebPPicture picture;
+ if (!WebPPictureInit(&picture))
+ {
+ SAL_WARN("vcl.filter.webp", "WebPPictureInit() failed");
+ return false;
+ }
+ picture.width = width;
+ picture.height = height;
+ picture.use_argb = lossless ? 1 : 0; // libwebp recommends argb only for lossless
+ // This unique_ptr is here just to ensure WebPPictureFree() is called at the end,
+ // it doesn't actually own the data as such.
+ std::unique_ptr<WebPPicture, decltype(&WebPPictureFree)> freePicture(&picture, WebPPictureFree);
+
+ // Apparently libwebp needs the entire image data at once in WebPPicture,
+ // so allocate it and copy there.
+ Bitmap bitmap(bitmapEx.GetBitmap());
+ AlphaMask bitmapAlpha;
+ if (bitmapEx.IsAlpha())
+ bitmapAlpha = bitmapEx.GetAlpha();
+ Bitmap::ScopedReadAccess access(bitmap);
+ AlphaMask::ScopedReadAccess accessAlpha(bitmapAlpha);
+ bool dataDone = false;
+ if (!access->IsBottomUp() && bitmapAlpha.IsEmpty())
+ {
+ // Try to directly copy the bitmap data.
+ switch (access->GetScanlineFormat())
+ {
+ case ScanlineFormat::N24BitTcRgb:
+ if (!WebPPictureImportRGB(&picture, access->GetBuffer(), access->GetScanlineSize()))
+ {
+ SAL_WARN("vcl.filter.webp", "WebPPictureImportRGB() failed");
+ return false;
+ }
+ dataDone = true;
+ break;
+ case ScanlineFormat::N24BitTcBgr:
+ if (!WebPPictureImportBGR(&picture, access->GetBuffer(), access->GetScanlineSize()))
+ {
+ SAL_WARN("vcl.filter.webp", "WebPPictureImportBGR() failed");
+ return false;
+ }
+ dataDone = true;
+ break;
+ // Our argb formats are premultiplied, so can't read directly using libwebp functions.
+ default:
+ break;
+ }
+ }
+ if (!dataDone)
+ {
+ // It would be simpler to convert the bitmap to 32bpp, but our 32bpp support is broken
+ // (it's unspecified whether it's premultiplied or not, for example). So handle this manually.
+ // Special handling for some common cases, generic otherwise.
+ if (!WebPPictureAlloc(&picture))
+ {
+ SAL_WARN("vcl.filter.webp", "WebPPictureAlloc() failed");
+ return false;
+ }
+ std::vector<uint8_t> data;
+ const int bpp = 4;
+ data.resize(width * height * bpp);
+ if (!bitmapAlpha.IsEmpty())
+ {
+ for (tools::Long y = 0; y < access->Height(); ++y)
+ {
+ unsigned char* dst = data.data() + width * bpp * y;
+ const sal_uInt8* srcB = access->GetScanline(y);
+ const sal_uInt8* srcA = accessAlpha->GetScanline(y);
+ for (tools::Long x = 0; x < access->Width(); ++x)
+ {
+ BitmapColor color = access->GetPixelFromData(srcB, x);
+ BitmapColor transparency = accessAlpha->GetPixelFromData(srcA, x);
+ color.SetTransparency(transparency.GetIndex());
+ dst[0] = color.GetRed();
+ dst[1] = color.GetGreen();
+ dst[2] = color.GetBlue();
+ dst[3] = 255 - color.GetTransparency();
+ dst += bpp;
+ }
+ }
+ }
+ else
+ {
+ for (tools::Long y = 0; y < access->Height(); ++y)
+ {
+ unsigned char* dst = data.data() + width * bpp * y;
+ const sal_uInt8* src = access->GetScanline(y);
+ for (tools::Long x = 0; x < access->Width(); ++x)
+ {
+ Color color = access->GetPixelFromData(src, x);
+ dst[0] = color.GetRed();
+ dst[1] = color.GetGreen();
+ dst[2] = color.GetBlue();
+ dst[3] = 255 - color.GetTransparency();
+ dst += bpp;
+ }
+ }
+ }
+ // And now import from the temporary data. Use the import function rather
+ // than writing the data directly to avoid the need to write the data
+ // in the exact format WebPPicture wants (YUV, etc.).
+ if (!WebPPictureImportRGBA(&picture, data.data(), width * bpp))
+ {
+ SAL_WARN("vcl.filter.webp", "WebPPictureImportRGBA() failed");
+ return false;
+ }
+ }
+
+ picture.writer = writerFunction;
+ picture.custom_ptr = &rStream;
+ return WebPEncode(&config, &picture);
+}
+
+bool ExportWebpGraphic(SvStream& rStream, const Graphic& rGraphic,
+ FilterConfigItem* pFilterConfigItem)
+{
+ BitmapEx bitmap = rGraphic.GetBitmapEx();
+ // If lossless, neither presets nor quality matter.
+ bool lossless = pFilterConfigItem->ReadBool("Lossless", true);
+ // Preset is WebPPreset values.
+ const OUString preset = pFilterConfigItem->ReadString("Preset", "");
+ int quality = pFilterConfigItem->ReadInt32("Quality", 75);
+ return writeWebp(rStream, bitmap, lossless, preset, quality);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/gdi/gfxlink.cxx b/vcl/source/gdi/gfxlink.cxx
index 83936c277ead..00da8ff5f1c4 100644
--- a/vcl/source/gdi/gfxlink.cxx
+++ b/vcl/source/gdi/gfxlink.cxx
@@ -137,6 +137,7 @@ bool GfxLink::LoadNative( Graphic& rGraphic )
case GfxLinkType::NativeSvg: aShortName = SVG_SHORTNAME; break;
case GfxLinkType::NativeBmp: aShortName = BMP_SHORTNAME; break;
case GfxLinkType::NativePdf: aShortName = PDF_SHORTNAME; break;
+ case GfxLinkType::NativeWebp: aShortName = WEBP_SHORTNAME; break;
default: break;
}
if (!aShortName.isEmpty())
diff --git a/vcl/source/graphic/UnoGraphicDescriptor.cxx b/vcl/source/graphic/UnoGraphicDescriptor.cxx
index 6f65828fe992..e966df7e64bd 100644
--- a/vcl/source/graphic/UnoGraphicDescriptor.cxx
+++ b/vcl/source/graphic/UnoGraphicDescriptor.cxx
@@ -127,6 +127,7 @@ void GraphicDescriptor::implCreate( SvStream& rIStm, const OUString* pURL )
case GraphicFileFormat::RAS: pMimeType = MIMETYPE_RAS; cType = graphic::GraphicType::PIXEL; break;
case GraphicFileFormat::TGA: pMimeType = MIMETYPE_TGA; cType = graphic::GraphicType::PIXEL; break;
case GraphicFileFormat::PSD: pMimeType = MIMETYPE_PSD; cType = graphic::GraphicType::PIXEL; break;
+ case GraphicFileFormat::WEBP: pMimeType = MIMETYPE_WEBP; cType = graphic::GraphicType::PIXEL; break;
case GraphicFileFormat::EPS: pMimeType = MIMETYPE_EPS; cType = graphic::GraphicType::VECTOR; break;
case GraphicFileFormat::DXF: pMimeType = MIMETYPE_DXF; cType = graphic::GraphicType::VECTOR; break;
@@ -298,6 +299,7 @@ void GraphicDescriptor::_getPropertyValues( const comphelper::PropertyMapEntry**
case GfxLinkType::NativeWmf: pMimeType = MIMETYPE_WMF; break;
case GfxLinkType::NativeMet: pMimeType = MIMETYPE_MET; break;
case GfxLinkType::NativePct: pMimeType = MIMETYPE_PCT; break;
+ case GfxLinkType::NativeWebp: pMimeType = MIMETYPE_WEBP; break;
// added Svg mimetype support
case GfxLinkType::NativeSvg: pMimeType = MIMETYPE_SVG; break;
diff --git a/vcl/workben/fftester.cxx b/vcl/workben/fftester.cxx
index 66b51a5d38e1..cbbdf07d9847 100644
--- a/vcl/workben/fftester.cxx
+++ b/vcl/workben/fftester.cxx
@@ -42,6 +42,7 @@
#include <vcl/wmf.hxx>
#include <vcl/wrkwin.hxx>
#include <fltcall.hxx>
+#include <filter/WebpReader.hxx>
#include <osl/file.hxx>
#include <osl/module.hxx>
#include <tools/stream.hxx>
@@ -321,6 +322,12 @@ SAL_IMPLEMENT_MAIN_WITH_ARGS(argc, argv)
SvFileStream aFileStream(out, StreamMode::READ);
ret = static_cast<int>((*pfnImport)(aFileStream, aGraphic, nullptr));
}
+ else if (strcmp(argv[2], "webp") == 0)
+ {
+ Graphic aGraphic;
+ SvFileStream aFileStream(out, StreamMode::READ);
+ ret = static_cast<int>(ImportWebpGraphic(aFileStream, aGraphic));
+ }
else if ((strcmp(argv[2], "doc") == 0) || (strcmp(argv[2], "ww8") == 0))
{
static FFilterCall pfnImport(nullptr);
diff --git a/vcl/workben/webpfuzzer.cxx b/vcl/workben/webpfuzzer.cxx
new file mode 100644
index 000000000000..b7ae8f741447
--- /dev/null
+++ b/vcl/workben/webpfuzzer.cxx
@@ -0,0 +1,48 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <tools/stream.hxx>
+#include <vcl/FilterConfigItem.hxx>
+#include "commonfuzzer.hxx"
+#include <filter/WebpReader.hxx>
+
+#include <config_features.h>
+#include <osl/detail/component-mapping.h>
+
+const lib_to_factory_mapping* lo_get_factory_map(void)
+{
+ static lib_to_factory_mapping map[] = { { 0, 0 } };
+
+ return map;
+}
+
+const lib_to_constructor_mapping* lo_get_constructor_map(void)
+{
+ static lib_to_constructor_mapping map[] = { { 0, 0 } };
+
+ return map;
+}
+
+extern "C" void* lo_get_custom_widget_func(const char*) { return nullptr; }
+
+extern "C" int LLVMFuzzerInitialize(int* argc, char*** argv)
+{
+ TypicalFuzzerInitialize(argc, argv);
+ return 0;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
+{
+ SvMemoryStream aStream(const_cast<uint8_t*>(data), size, StreamMode::READ);
+ Graphic aGraphic;
+ (void)ImportWebpGraphic(aStream, aGraphic);
+ return 0;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/workben/webpfuzzer.options b/vcl/workben/webpfuzzer.options
new file mode 100644
index 000000000000..678d526b1ea9
--- /dev/null
+++ b/vcl/workben/webpfuzzer.options
@@ -0,0 +1,2 @@
+[libfuzzer]
+max_len = 65536