summaryrefslogtreecommitdiffstats
path: root/vcl/qa/cppunit/BitmapFilterTest.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'vcl/qa/cppunit/BitmapFilterTest.cxx')
-rw-r--r--vcl/qa/cppunit/BitmapFilterTest.cxx283
1 files changed, 283 insertions, 0 deletions
diff --git a/vcl/qa/cppunit/BitmapFilterTest.cxx b/vcl/qa/cppunit/BitmapFilterTest.cxx
new file mode 100644
index 000000000..a2aa37ceb
--- /dev/null
+++ b/vcl/qa/cppunit/BitmapFilterTest.cxx
@@ -0,0 +1,283 @@
+/* -*- 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 <test/bootstrapfixture.hxx>
+
+#include <vcl/bitmap.hxx>
+#include <bitmap/BitmapWriteAccess.hxx>
+
+#include <tools/stream.hxx>
+#include <vcl/graphicfilter.hxx>
+
+#include <vcl/BitmapBasicMorphologyFilter.hxx>
+#include <vcl/BitmapFilterStackBlur.hxx>
+#include <BitmapSymmetryCheck.hxx>
+
+#include <chrono>
+#include <string_view>
+
+namespace
+{
+constexpr bool constWriteResultBitmap(false);
+constexpr bool constEnablePerformanceTest(false);
+
+class BitmapFilterTest : public test::BootstrapFixture
+{
+public:
+ BitmapFilterTest()
+ : test::BootstrapFixture(true, false)
+ {
+ }
+
+ void testBlurCorrectness();
+ void testBasicMorphology();
+ void testPerformance();
+ void testGenerateStripRanges();
+
+ CPPUNIT_TEST_SUITE(BitmapFilterTest);
+ CPPUNIT_TEST(testBlurCorrectness);
+ CPPUNIT_TEST(testBasicMorphology);
+ CPPUNIT_TEST(testPerformance);
+ CPPUNIT_TEST(testGenerateStripRanges);
+ CPPUNIT_TEST_SUITE_END();
+
+private:
+ OUString getFullUrl(std::u16string_view sFileName)
+ {
+ return m_directories.getURLFromSrc(u"vcl/qa/cppunit/data/") + sFileName;
+ }
+
+ BitmapEx loadBitmap(std::u16string_view sFileName)
+ {
+ Graphic aGraphic;
+ const OUString aURL(getFullUrl(sFileName));
+ SvFileStream aFileStream(aURL, StreamMode::READ);
+ GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter();
+ ErrCode aResult = rFilter.ImportGraphic(aGraphic, aURL, aFileStream);
+ CPPUNIT_ASSERT_EQUAL(ERRCODE_NONE, aResult);
+ return aGraphic.GetBitmapEx();
+ }
+
+ template <class BitmapT> // handle both Bitmap and BitmapEx
+ void savePNG(const OUString& sWhere, const BitmapT& rBmp)
+ {
+ SvFileStream aStream(sWhere, StreamMode::WRITE | StreamMode::TRUNC);
+ GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter();
+ rFilter.compressAsPNG(BitmapEx(rBmp), aStream);
+ }
+};
+
+void BitmapFilterTest::testBlurCorrectness()
+{
+ // Setup test bitmap
+ Size aSize(41, 31);
+ Bitmap aBitmap24Bit(aSize, vcl::PixelFormat::N24_BPP);
+
+ ScanlineFormat scanlineFormat = ScanlineFormat::NONE;
+ auto ePixelFormat = aBitmap24Bit.getPixelFormat();
+
+ {
+ tools::Long aMargin1 = 1;
+ tools::Long aMargin2 = 3;
+ BitmapScopedWriteAccess aWriteAccess(aBitmap24Bit);
+ scanlineFormat = aWriteAccess->GetScanlineFormat();
+ aWriteAccess->Erase(COL_WHITE);
+ aWriteAccess->SetLineColor(COL_BLACK);
+
+ tools::Rectangle aRectangle1(aMargin1, aMargin1, aSize.Width() - 1 - aMargin1,
+ aSize.Height() - 1 - aMargin1);
+
+ tools::Rectangle aRectangle2(aMargin2, aMargin2, aSize.Width() - 1 - aMargin2,
+ aSize.Height() - 1 - aMargin2);
+
+ tools::Rectangle aRectangle3(aSize.Width() / 2, aSize.Height() / 2, aSize.Width() / 2,
+ aSize.Height() / 2);
+
+ aWriteAccess->DrawRect(aRectangle1);
+ aWriteAccess->DrawRect(aRectangle2);
+ aWriteAccess->DrawRect(aRectangle3);
+ }
+
+ if (constWriteResultBitmap)
+ {
+ savePNG("~/blurBefore.png", aBitmap24Bit);
+ }
+
+ // Perform blur
+ BitmapFilterStackBlur aBlurFilter(2);
+ aBitmap24Bit = aBlurFilter.filter(aBitmap24Bit);
+
+ // Check the result
+
+ if (constWriteResultBitmap)
+ {
+ savePNG("~/blurAfter.png", aBitmap24Bit);
+ }
+
+ // Check blurred bitmap parameters
+ CPPUNIT_ASSERT_EQUAL(static_cast<tools::Long>(41), aBitmap24Bit.GetSizePixel().Width());
+ CPPUNIT_ASSERT_EQUAL(static_cast<tools::Long>(31), aBitmap24Bit.GetSizePixel().Height());
+
+ CPPUNIT_ASSERT_EQUAL(ePixelFormat, aBitmap24Bit.getPixelFormat());
+
+ // Check that the bitmap is horizontally and vertically symmetrical
+ CPPUNIT_ASSERT(BitmapSymmetryCheck::check(aBitmap24Bit));
+
+ {
+ Bitmap::ScopedReadAccess aReadAccess(aBitmap24Bit);
+ CPPUNIT_ASSERT_EQUAL(scanlineFormat, aReadAccess->GetScanlineFormat());
+ }
+}
+
+void BitmapFilterTest::testBasicMorphology()
+{
+ const BitmapEx aOrigBitmap = loadBitmap(u"testBasicMorphology.png");
+ const BitmapEx aRefBitmapDilated1 = loadBitmap(u"testBasicMorphologyDilated1.png");
+ const BitmapEx aRefBitmapDilated1Eroded1
+ = loadBitmap(u"testBasicMorphologyDilated1Eroded1.png");
+ const BitmapEx aRefBitmapDilated2 = loadBitmap(u"testBasicMorphologyDilated2.png");
+ const BitmapEx aRefBitmapDilated2Eroded1
+ = loadBitmap(u"testBasicMorphologyDilated2Eroded1.png");
+
+ BitmapEx aTransformBitmap = aOrigBitmap;
+ BitmapFilter::Filter(aTransformBitmap, BitmapDilateFilter(1));
+ if (constWriteResultBitmap)
+ savePNG("~/Dilated1.png", aTransformBitmap);
+ CPPUNIT_ASSERT_EQUAL(aRefBitmapDilated1.GetChecksum(), aTransformBitmap.GetChecksum());
+ BitmapFilter::Filter(aTransformBitmap, BitmapErodeFilter(1));
+ if (constWriteResultBitmap)
+ savePNG("~/Dilated1Eroded1.png", aTransformBitmap);
+ CPPUNIT_ASSERT_EQUAL(aRefBitmapDilated1Eroded1.GetChecksum(), aTransformBitmap.GetChecksum());
+
+ aTransformBitmap = aOrigBitmap;
+ BitmapFilter::Filter(aTransformBitmap, BitmapDilateFilter(2));
+ if (constWriteResultBitmap)
+ savePNG("~/Dilated2.png", aTransformBitmap);
+ CPPUNIT_ASSERT_EQUAL(aRefBitmapDilated2.GetChecksum(), aTransformBitmap.GetChecksum());
+ BitmapFilter::Filter(aTransformBitmap, BitmapErodeFilter(1));
+ if (constWriteResultBitmap)
+ savePNG("~/Dilated2Eroded1.png", aTransformBitmap);
+ CPPUNIT_ASSERT_EQUAL(aRefBitmapDilated2Eroded1.GetChecksum(), aTransformBitmap.GetChecksum());
+}
+
+void BitmapFilterTest::testPerformance()
+{
+ if (!constEnablePerformanceTest)
+ return;
+
+ Size aSize(4000, 3000); // A rather common picture size
+
+ // Prepare bitmap
+ Bitmap aBigBitmap(aSize, vcl::PixelFormat::N24_BPP);
+ {
+ tools::Long aMargin = 500;
+ BitmapScopedWriteAccess aWriteAccess(aBigBitmap);
+ aWriteAccess->Erase(COL_WHITE);
+ aWriteAccess->SetLineColor(COL_BLACK);
+ aWriteAccess->SetFillColor(COL_BLACK);
+ tools::Rectangle aRectangle(aMargin, aMargin, aSize.Width() - 1 - aMargin,
+ aSize.Height() - 1 - aMargin);
+
+ aWriteAccess->DrawRect(aRectangle);
+ }
+
+ int nIterations = 10;
+ auto start = std::chrono::high_resolution_clock::now();
+ Bitmap aResult;
+ for (int i = 0; i < nIterations; i++)
+ {
+ BitmapFilterStackBlur aBlurFilter(250);
+ aResult = aBlurFilter.filter(aBigBitmap);
+ }
+ auto end = std::chrono::high_resolution_clock::now();
+ auto elapsed = (end - start) / nIterations;
+
+ if (constWriteResultBitmap)
+ {
+ std::unique_ptr<SvFileStream> pStream(
+ new SvFileStream("~/BlurBigPerformance.png", StreamMode::WRITE | StreamMode::TRUNC));
+ GraphicFilter& rFilter = GraphicFilter::GetGraphicFilter();
+ rFilter.compressAsPNG(BitmapEx(aResult), *pStream);
+
+ pStream.reset(new SvFileStream("~/BlurBigPerformance.txt", StreamMode::WRITE));
+ pStream->WriteOString("Blur average time: ");
+ pStream->WriteOString(OString::number(
+ std::chrono::duration_cast<std::chrono::milliseconds>(elapsed).count()));
+ pStream->WriteOString("\n");
+ }
+}
+
+void BitmapFilterTest::testGenerateStripRanges()
+{
+ {
+ constexpr tools::Long nFirstIndex = 0;
+ constexpr tools::Long nLastIndex = 100;
+ constexpr tools::Long nStripSize = 32;
+
+ std::vector<std::tuple<tools::Long, tools::Long, bool>> aRanges;
+
+ vcl::bitmap::generateStripRanges<nStripSize>(
+ nFirstIndex, nLastIndex,
+ [&](tools::Long const nStart, tools::Long const nEnd, bool const bLast) {
+ aRanges.emplace_back(nStart, nEnd, bLast);
+ });
+
+ CPPUNIT_ASSERT_EQUAL(size_t(4), aRanges.size());
+
+ CPPUNIT_ASSERT_EQUAL(tools::Long(0), std::get<0>(aRanges[0]));
+ CPPUNIT_ASSERT_EQUAL(tools::Long(31), std::get<1>(aRanges[0]));
+ CPPUNIT_ASSERT_EQUAL(false, std::get<2>(aRanges[0]));
+
+ CPPUNIT_ASSERT_EQUAL(tools::Long(32), std::get<0>(aRanges[1]));
+ CPPUNIT_ASSERT_EQUAL(tools::Long(63), std::get<1>(aRanges[1]));
+ CPPUNIT_ASSERT_EQUAL(false, std::get<2>(aRanges[1]));
+
+ CPPUNIT_ASSERT_EQUAL(tools::Long(64), std::get<0>(aRanges[2]));
+ CPPUNIT_ASSERT_EQUAL(tools::Long(95), std::get<1>(aRanges[2]));
+ CPPUNIT_ASSERT_EQUAL(false, std::get<2>(aRanges[2]));
+
+ CPPUNIT_ASSERT_EQUAL(tools::Long(96), std::get<0>(aRanges[3]));
+ CPPUNIT_ASSERT_EQUAL(tools::Long(100), std::get<1>(aRanges[3]));
+ CPPUNIT_ASSERT_EQUAL(true, std::get<2>(aRanges[3]));
+ }
+
+ {
+ constexpr tools::Long nFirstIndex = 0;
+ constexpr tools::Long nLastIndex = 95;
+ constexpr tools::Long nStripSize = 32;
+
+ std::vector<std::tuple<tools::Long, tools::Long, bool>> aRanges;
+
+ vcl::bitmap::generateStripRanges<nStripSize>(
+ nFirstIndex, nLastIndex,
+ [&](tools::Long const nStart, tools::Long const nEnd, bool const bLast) {
+ aRanges.emplace_back(nStart, nEnd, bLast);
+ });
+
+ CPPUNIT_ASSERT_EQUAL(size_t(3), aRanges.size());
+
+ CPPUNIT_ASSERT_EQUAL(tools::Long(0), std::get<0>(aRanges[0]));
+ CPPUNIT_ASSERT_EQUAL(tools::Long(31), std::get<1>(aRanges[0]));
+ CPPUNIT_ASSERT_EQUAL(false, std::get<2>(aRanges[0]));
+
+ CPPUNIT_ASSERT_EQUAL(tools::Long(32), std::get<0>(aRanges[1]));
+ CPPUNIT_ASSERT_EQUAL(tools::Long(63), std::get<1>(aRanges[1]));
+ CPPUNIT_ASSERT_EQUAL(false, std::get<2>(aRanges[1]));
+
+ CPPUNIT_ASSERT_EQUAL(tools::Long(64), std::get<0>(aRanges[2]));
+ CPPUNIT_ASSERT_EQUAL(tools::Long(95), std::get<1>(aRanges[2]));
+ CPPUNIT_ASSERT_EQUAL(true, std::get<2>(aRanges[2]));
+ }
+}
+
+} // namespace
+
+CPPUNIT_TEST_SUITE_REGISTRATION(BitmapFilterTest);
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */