diff options
Diffstat (limited to 'vcl')
33 files changed, 1055 insertions, 3 deletions
diff --git a/vcl/CppunitTest_vcl_filters_test.mk b/vcl/CppunitTest_vcl_filters_test.mk index ab5108f51b1c..f9f8e40cca98 100644 --- a/vcl/CppunitTest_vcl_filters_test.mk +++ b/vcl/CppunitTest_vcl_filters_test.mk @@ -22,6 +22,7 @@ $(eval $(call gb_CppunitTest_add_exception_objects,vcl_filters_test, \ vcl/qa/cppunit/graphicfilter/filters-test \ vcl/qa/cppunit/graphicfilter/filters-tiff-test \ vcl/qa/cppunit/graphicfilter/filters-tga-test \ + vcl/qa/cppunit/graphicfilter/filters-webp-test \ )) $(eval $(call gb_CppunitTest_set_include,vcl_filters_test,\ 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 7886b5b271b5..90d432ee559b 100644 --- a/vcl/Library_vcl.mk +++ b/vcl/Library_vcl.mk @@ -89,6 +89,7 @@ $(eval $(call gb_Library_use_externals,vcl,\ libeot \ libjpeg \ libpng \ + libwebp \ mdds_headers \ $(if $(filter PDFIUM,$(BUILD_TYPE)),pdfium) \ )) @@ -470,6 +471,8 @@ $(eval $(call gb_Library_add_exception_objects,vcl,\ vcl/source/filter/wmf/wmfwr \ vcl/source/filter/png/PngImageReader \ vcl/source/filter/png/pngwrite \ + vcl/source/filter/webp/reader \ + vcl/source/filter/webp/writer \ vcl/source/font/DirectFontSubstitution \ vcl/source/font/Feature \ vcl/source/font/FeatureCollector \ diff --git a/vcl/Module_vcl.mk b/vcl/Module_vcl.mk index f85bac2f1cb7..23a45b358da3 100644 --- a/vcl/Module_vcl.mk +++ b/vcl/Module_vcl.mk @@ -199,6 +199,7 @@ $(eval $(call gb_Module_add_targets,vcl,\ Executable_htmlfuzzer \ Executable_sftfuzzer \ Executable_dbffuzzer \ + 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 f2f0393caeee..34a8b16a2c5d 100644 --- a/vcl/inc/graphic/GraphicFormatDetector.hxx +++ b/vcl/inc/graphic/GraphicFormatDetector.hxx @@ -77,6 +77,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 6a233e09ae16..096b30fd694e 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" inline constexpr OUStringLiteral MIMETYPE_VCLGRAPHIC = u"image/x-vclgraphic"; namespace comphelper { class PropertySetInfo; } diff --git a/vcl/qa/cppunit/GraphicDescriptorTest.cxx b/vcl/qa/cppunit/GraphicDescriptorTest.cxx index 652393ae9b63..865202cedbf5 100644 --- a/vcl/qa/cppunit/GraphicDescriptorTest.cxx +++ b/vcl/qa/cppunit/GraphicDescriptorTest.cxx @@ -34,6 +34,7 @@ class GraphicDescriptorTest : public test::BootstrapFixtureBase void testDetectGIF(); void testDetectTIF(); void testDetectBMP(); + void testDetectWEBP(); CPPUNIT_TEST_SUITE(GraphicDescriptorTest); CPPUNIT_TEST(testDetectPNG); @@ -41,6 +42,7 @@ class GraphicDescriptorTest : public test::BootstrapFixtureBase CPPUNIT_TEST(testDetectGIF); CPPUNIT_TEST(testDetectTIF); CPPUNIT_TEST(testDetectBMP); + CPPUNIT_TEST(testDetectWEBP); CPPUNIT_TEST_SUITE_END(); }; @@ -138,6 +140,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 264a0e8cd48d..4c16bfc82e1b 100644 --- a/vcl/qa/cppunit/GraphicFormatDetectorTest.cxx +++ b/vcl/qa/cppunit/GraphicFormatDetectorTest.cxx @@ -47,6 +47,7 @@ class GraphicFormatDetectorTest : public test::BootstrapFixtureBase void testDetectSVGZ(); void testDetectPDF(); void testDetectEPS(); + void testDetectWEBP(); void testMatchArray(); void testCheckArrayForMatchingStrings(); @@ -67,6 +68,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(); @@ -312,6 +314,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(vcl::peekGraphicFormat(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 44ad1df12829..2e398c544c79 100644 --- a/vcl/qa/cppunit/GraphicTest.cxx +++ b/vcl/qa/cppunit/GraphicTest.cxx @@ -81,6 +81,7 @@ private: void testLoadXPM(); void testLoadPCX(); void testLoadEPS(); + void testLoadWEBP(); void testAvailableThreaded(); @@ -118,6 +119,7 @@ private: CPPUNIT_TEST(testLoadXPM); CPPUNIT_TEST(testLoadPCX); CPPUNIT_TEST(testLoadEPS); + CPPUNIT_TEST(testLoadWEBP); CPPUNIT_TEST(testAvailableThreaded); @@ -305,7 +307,7 @@ void GraphicTest::testUnloadedGraphic() void GraphicTest::testUnloadedGraphicLoading() { - const OUString aFormats[] = { "png", "gif", "jpg", "tif" }; + const OUString aFormats[] = { "png", "gif", "jpg", "tif", "webp" }; for (OUString const& sFormat : aFormats) { @@ -1310,6 +1312,14 @@ void GraphicTest::testLoadEPS() CPPUNIT_ASSERT_EQUAL(GraphicType::GdiMetafile, aGraphic.GetType()); } +void GraphicTest::testLoadWEBP() +{ + Graphic aGraphic = loadGraphic(u"TypeDetectionExample.webp"); + CPPUNIT_ASSERT_EQUAL(GraphicType::Bitmap, aGraphic.GetType()); + CPPUNIT_ASSERT_EQUAL(tools::Long(10), aGraphic.GetSizePixel().Width()); + CPPUNIT_ASSERT_EQUAL(tools::Long(10), aGraphic.GetSizePixel().Height()); +} + void GraphicTest::testAvailableThreaded() { Graphic jpgGraphic1 = importUnloadedGraphic(u"TypeDetectionExample.jpg"); diff --git a/vcl/qa/cppunit/data/TypeDetectionExample.webp b/vcl/qa/cppunit/data/TypeDetectionExample.webp Binary files differnew file mode 100644 index 000000000000..e85ae121637b --- /dev/null +++ b/vcl/qa/cppunit/data/TypeDetectionExample.webp diff --git a/vcl/qa/cppunit/graphicfilter/data/webp/alpha_lossless.webp b/vcl/qa/cppunit/graphicfilter/data/webp/alpha_lossless.webp Binary files differnew file mode 100644 index 000000000000..abb67cc5f4b6 --- /dev/null +++ b/vcl/qa/cppunit/graphicfilter/data/webp/alpha_lossless.webp diff --git a/vcl/qa/cppunit/graphicfilter/data/webp/alpha_lossy.webp b/vcl/qa/cppunit/graphicfilter/data/webp/alpha_lossy.webp Binary files differnew file mode 100644 index 000000000000..c587b1bb22c0 --- /dev/null +++ b/vcl/qa/cppunit/graphicfilter/data/webp/alpha_lossy.webp 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 Binary files differnew file mode 100644 index 000000000000..b22ebc3b28bb --- /dev/null +++ b/vcl/qa/cppunit/graphicfilter/data/webp/noalpha_lossless.webp diff --git a/vcl/qa/cppunit/graphicfilter/data/webp/noalpha_lossy.webp b/vcl/qa/cppunit/graphicfilter/data/webp/noalpha_lossy.webp Binary files differnew file mode 100644 index 000000000000..c118febb0e9a --- /dev/null +++ b/vcl/qa/cppunit/graphicfilter/data/webp/noalpha_lossy.webp 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 22078cf9de65..f10de06fc837 100644 --- a/vcl/qa/cppunit/graphicfilter/filters-test.cxx +++ b/vcl/qa/cppunit/graphicfilter/filters-test.cxx @@ -145,6 +145,8 @@ void VclFiltersTest::testExportImport() checkExportImport(u"bmp"); fprintf(stderr, "Check ExportImport TIF\n"); checkExportImport(u"tif"); + 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..90528199c0ba --- /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 <bitmap/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), vcl::PixelFormat::N24_BPP); + 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 370e8cffed8b..3430d0251cd7 100644 --- a/vcl/source/filter/FilterConfigCache.cxx +++ b/vcl/source/filter/FilterConfigCache.cxx @@ -41,10 +41,10 @@ using namespace ::com::sun::star::configuration ; const char* FilterConfigCache::FilterConfigCacheEntry::InternalPixelFilterNameList[] = { - IMP_BMP, IMP_GIF, IMP_PNG, IMP_JPEG, IMP_TIFF, + IMP_BMP, IMP_GIF, IMP_PNG, IMP_JPEG, IMP_TIFF, IMP_WEBP, IMP_XBM, IMP_XPM, IMP_TGA, IMP_PICT, IMP_MET, IMP_RAS, IMP_PCX, IMP_MOV, IMP_PSD, IMP_PCD, IMP_PBM, IMP_DXF, - EXP_BMP, EXP_GIF, EXP_PNG, EXP_JPEG, EXP_TIFF, + EXP_BMP, EXP_GIF, EXP_PNG, EXP_JPEG, EXP_TIFF, EXP_WEBP, nullptr }; @@ -226,6 +226,8 @@ const char* FilterConfigCache::InternalFilterListForSvxLight[] = "xpm","1","SVIXPM", "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 6c8df32a456e..2dddcfa0f9ae 100644 --- a/vcl/source/filter/GraphicFormatDetector.cxx +++ b/vcl/source/filter/GraphicFormatDetector.cxx @@ -274,6 +274,16 @@ bool peekGraphicFormat(SvStream& rStream, OUString& rFormatExtension, bool bTest } } + if (!bTest || rFormatExtension.startsWith("WEBP")) + { + bSomethingTested = true; + if (aDetector.checkWEBP()) + { + rFormatExtension = aDetector.msDetectedFormat; + return true; + } + } + return bTest && !bSomethingTested; } @@ -816,6 +826,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 add513e1b72e..17710e85650c 100644 --- a/vcl/source/filter/graphicfilter.cxx +++ b/vcl/source/filter/graphicfilter.cxx @@ -64,6 +64,8 @@ #include <filter/GifWriter.hxx> #include <filter/BmpReader.hxx> #include <filter/BmpWriter.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> @@ -82,6 +84,7 @@ #include <memory> #include <mutex> #include <string_view> +#include <o3tl/string_view.hxx> #include <vcl/TypeSerializer.hxx> #include "FilterConfigCache.hxx" @@ -90,6 +93,24 @@ #include <graphic/GraphicFormatDetector.hxx> #include <graphic/GraphicReader.hxx> +// 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. +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 + || o3tl::starts_with(std::string_view(testname), "WebpFilterTest__")) + return true; + return false; +} + static std::vector< GraphicFilter* > gaFilterHdlList; static std::mutex& getListMutex() @@ -899,6 +920,13 @@ Graphic GraphicFilter::ImportUnloadedGraphic(SvStream& rIStream, sal_uInt64 size { eLinkType = GfxLinkType::NativeMet; } + else if (aFilterName.equalsIgnoreAsciiCase(IMP_WEBP)) + { + if(supportNativeWebp()) + eLinkType = GfxLinkType::NativeWebp; + else + nStatus = ERRCODE_GRFILTER_FILTERERROR; + } else { nStatus = ERRCODE_GRFILTER_FILTERERROR; @@ -1305,6 +1333,18 @@ ErrCode GraphicFilter::readDXF(SvStream & rStream, Graphic & rGraphic) return ERRCODE_GRFILTER_FILTERERROR; } +ErrCode GraphicFilter::readWEBP(SvStream & rStream, Graphic & rGraphic, GfxLinkType & rLinkType) +{ + if (ImportWebpGraphic(rStream, rGraphic)) + { + if(supportNativeWebp()) + rLinkType = GfxLinkType::NativeWebp; + return ERRCODE_NONE; + } + else + return ERRCODE_GRFILTER_FILTERERROR; +} + ErrCode GraphicFilter::ImportGraphic( Graphic& rGraphic, const OUString& rPath, SvStream& rIStream, sal_uInt16 nFormat, sal_uInt16* pDeterminedFormat, GraphicFilterImportFlags nImportFlags, const css::uno::Sequence< css::beans::PropertyValue >* /*pFilterData*/, @@ -1455,6 +1495,10 @@ ErrCode GraphicFilter::ImportGraphic( Graphic& rGraphic, const OUString& rPath, { nStatus = readDXF(rIStream, rGraphic); } + else if (aFilterName.equalsIgnoreAsciiCase(IMP_WEBP)) + { + nStatus = readWEBP(rIStream, rGraphic, eLinkType); + } else nStatus = ERRCODE_GRFILTER_FILTERERROR; } @@ -1840,6 +1884,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; } @@ -1882,6 +1934,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 0360c51d3347..5f73d570d9f6 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 ); } @@ -1142,6 +1144,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; @@ -1171,6 +1203,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..e8c885053965 --- /dev/null +++ b/vcl/source/filter/webp/reader.cxx @@ -0,0 +1,318 @@ +/* -*- 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 <bitmap/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(); + const bool bSupportsBitmap32 = bFuzzing || ImplGetSVData()->mpDefInst->supportsBitmap32(); + + Bitmap bitmap; + AlphaMask bitmapAlpha; + if (bSupportsBitmap32 && has_alpha) + { + bitmap = Bitmap(Size(width, height), vcl::PixelFormat::N32_BPP); + } + else + { + bitmap = Bitmap(Size(width, height), vcl::PixelFormat::N24_BPP); + 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(ColorAlpha, 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..32da25e93ca3 --- /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/BitmapReadAccess.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.SetAlpha(255 - transparency.GetIndex()); + dst[0] = color.GetRed(); + dst[1] = color.GetGreen(); + dst[2] = color.GetBlue(); + dst[3] = color.GetAlpha(); + 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] = color.GetAlpha(); + 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 147f89febc85..0baddbf931ae 100644 --- a/vcl/source/gdi/gfxlink.cxx +++ b/vcl/source/gdi/gfxlink.cxx @@ -134,6 +134,7 @@ bool GfxLink::LoadNative( Graphic& rGraphic ) const 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/gdi/mtfxmldump.cxx b/vcl/source/gdi/mtfxmldump.cxx index 11c76ac5af5c..52acaff36e09 100644 --- a/vcl/source/gdi/mtfxmldump.cxx +++ b/vcl/source/gdi/mtfxmldump.cxx @@ -466,6 +466,7 @@ OUString convertGfxLinkTypeToString(GfxLinkType eGfxLinkType) case GfxLinkType::NativeSvg: return "NativeSvg"; case GfxLinkType::NativeTif: return "NativeTif"; case GfxLinkType::NativeWmf: return "NativeWmf"; + case GfxLinkType::NativeWebp: return "NativeWebp"; case GfxLinkType::NONE: return "None"; } return OUString(); diff --git a/vcl/source/graphic/UnoGraphicDescriptor.cxx b/vcl/source/graphic/UnoGraphicDescriptor.cxx index 60f2dcaa4c8b..a4d21c4420e6 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 218909dde6c7..d69c07708841 100644 --- a/vcl/workben/fftester.cxx +++ b/vcl/workben/fftester.cxx @@ -62,6 +62,7 @@ #include <filter/PcdReader.hxx> #include <filter/PbmReader.hxx> #include <filter/DxfReader.hxx> +#include <filter/WebpReader.hxx> #include <filter/XpmReader.hxx> #include <osl/file.hxx> #include <osl/module.hxx> @@ -252,6 +253,12 @@ SAL_IMPLEMENT_MAIN_WITH_ARGS(argc, argv) SvFileStream aFileStream(out, StreamMode::READ); ret = static_cast<int>(ImportTiffGraphicImport(aFileStream, aGraphic)); } + else if (strcmp(argv[2], "webp") == 0) + { + Graphic aGraphic; + SvFileStream aFileStream(out, StreamMode::READ); + ret = static_cast<int>(ImportWebpGraphic(aFileStream, aGraphic)); + } #ifndef DISABLE_DYNLOADING else if ((strcmp(argv[2], "doc") == 0) || (strcmp(argv[2], "ww8") == 0)) { 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 |