diff options
Diffstat (limited to 'vcl')
-rw-r--r-- | vcl/qa/cppunit/GraphicTest.cxx | 28 | ||||
-rw-r--r-- | vcl/source/filter/graphicfilter.cxx | 73 | ||||
-rw-r--r-- | vcl/source/filter/png/PngImageReader.cxx | 164 | ||||
-rw-r--r-- | vcl/source/filter/png/png.hxx | 33 |
4 files changed, 230 insertions, 68 deletions
diff --git a/vcl/qa/cppunit/GraphicTest.cxx b/vcl/qa/cppunit/GraphicTest.cxx index a64ea67cdb78..d323a6cf5869 100644 --- a/vcl/qa/cppunit/GraphicTest.cxx +++ b/vcl/qa/cppunit/GraphicTest.cxx @@ -80,6 +80,8 @@ private: void testLoadPCX(); void testLoadEPS(); + void testAvailableThreaded(); + CPPUNIT_TEST_SUITE(GraphicTest); CPPUNIT_TEST(testUnloadedGraphic); CPPUNIT_TEST(testUnloadedGraphicLoading); @@ -114,6 +116,8 @@ private: CPPUNIT_TEST(testLoadPCX); CPPUNIT_TEST(testLoadEPS); + CPPUNIT_TEST(testAvailableThreaded); + CPPUNIT_TEST_SUITE_END(); }; @@ -247,6 +251,15 @@ Graphic loadGraphic(std::u16string_view const& rFilename) return aGraphic; } +Graphic importUnloadedGraphic(std::u16string_view const& rFilename) +{ + test::Directories aDirectories; + OUString aFilename = aDirectories.getURLFromSrc(DATA_DIRECTORY) + rFilename; + SvFileStream aFileStream(aFilename, StreamMode::READ); + GraphicFilter& rGraphicFilter = GraphicFilter::GetGraphicFilter(); + return rGraphicFilter.ImportUnloadedGraphic(aFileStream); +} + void GraphicTest::testUnloadedGraphic() { // make unloaded test graphic @@ -1219,6 +1232,21 @@ void GraphicTest::testLoadEPS() CPPUNIT_ASSERT_EQUAL(GraphicType::GdiMetafile, aGraphic.GetType()); } +void GraphicTest::testAvailableThreaded() +{ + Graphic jpgGraphic1 = importUnloadedGraphic(u"TypeDetectionExample.jpg"); + Graphic jpgGraphic2 = importUnloadedGraphic(u"Exif1.jpg"); + Graphic pngGraphic1 = importUnloadedGraphic(u"TypeDetectionExample.png"); + Graphic pngGraphic2 = importUnloadedGraphic(u"testBasicMorphology.png"); + std::vector<Graphic*> graphics = { &jpgGraphic1, &jpgGraphic2, &pngGraphic1, &pngGraphic2 }; + for (auto& graphic : graphics) + CPPUNIT_ASSERT(!graphic->isAvailable()); + GraphicFilter& graphicFilter = GraphicFilter::GetGraphicFilter(); + graphicFilter.MakeGraphicsAvailableThreaded(graphics); + for (auto& graphic : graphics) + CPPUNIT_ASSERT(graphic->isAvailable()); +} + } // namespace CPPUNIT_TEST_SUITE_REGISTRATION(GraphicTest); diff --git a/vcl/source/filter/graphicfilter.cxx b/vcl/source/filter/graphicfilter.cxx index cd29c5088a0e..9de2f801c790 100644 --- a/vcl/source/filter/graphicfilter.cxx +++ b/vcl/source/filter/graphicfilter.cxx @@ -44,6 +44,7 @@ #include "igif/gifread.hxx" #include <vcl/pdfread.hxx> #include "jpeg/jpeg.hxx" +#include "png/png.hxx" #include "ixbm/xbmread.hxx" #include <filter/XpmReader.hxx> #include <filter/TiffReader.hxx> @@ -545,6 +546,9 @@ struct GraphicImportContext std::shared_ptr<Graphic> m_pGraphic; /// Write pixel data using this access. std::unique_ptr<BitmapScopedWriteAccess> m_pAccess; + std::unique_ptr<AlphaScopedWriteAccess> m_pAlphaAccess; + // Need to have an AlphaMask instance to keep its lifetime. + AlphaMask mAlphaMask; /// Signals if import finished correctly. ErrCode m_nStatus = ERRCODE_GRFILTER_FILTERERROR; /// Original graphic format. @@ -581,10 +585,20 @@ void GraphicImportTask::doWork() void GraphicImportTask::doImport(GraphicImportContext& rContext) { - if (!ImportJPEG(*rContext.m_pStream, *rContext.m_pGraphic, rContext.m_nImportFlags | GraphicFilterImportFlags::UseExistingBitmap, rContext.m_pAccess.get())) - rContext.m_nStatus = ERRCODE_GRFILTER_FILTERERROR; - else - rContext.m_eLinkType = GfxLinkType::NativeJpg; + if(rContext.m_eLinkType == GfxLinkType::NativeJpg) + { + if (!ImportJPEG(*rContext.m_pStream, *rContext.m_pGraphic, rContext.m_nImportFlags | GraphicFilterImportFlags::UseExistingBitmap, rContext.m_pAccess.get())) + rContext.m_nStatus = ERRCODE_GRFILTER_FILTERERROR; + } + else if(rContext.m_eLinkType == GfxLinkType::NativePng) + { + if (!vcl::ImportPNG(*rContext.m_pStream, *rContext.m_pGraphic, + rContext.m_nImportFlags | GraphicFilterImportFlags::UseExistingBitmap, + rContext.m_pAccess.get(), rContext.m_pAlphaAccess.get())) + { + rContext.m_nStatus = ERRCODE_GRFILTER_FILTERERROR; + } + } } void GraphicFilter::ImportGraphics(std::vector< std::shared_ptr<Graphic> >& rGraphics, std::vector< std::unique_ptr<SvStream> > vStreams) @@ -620,11 +634,10 @@ void GraphicFilter::ImportGraphics(std::vector< std::shared_ptr<Graphic> >& rGra if (aFilterName.equalsIgnoreAsciiCase(IMP_JPEG)) { + rContext.m_eLinkType = GfxLinkType::NativeJpg; rContext.m_nImportFlags = GraphicFilterImportFlags::SetLogsizeForJpeg; - if (!ImportJPEG( *rContext.m_pStream, *rContext.m_pGraphic, rContext.m_nImportFlags | GraphicFilterImportFlags::OnlyCreateBitmap, nullptr)) - rContext.m_nStatus = ERRCODE_GRFILTER_FILTERERROR; - else + if (ImportJPEG( *rContext.m_pStream, *rContext.m_pGraphic, rContext.m_nImportFlags | GraphicFilterImportFlags::OnlyCreateBitmap, nullptr)) { Bitmap& rBitmap = const_cast<Bitmap&>(rContext.m_pGraphic->GetBitmapExRef().GetBitmap()); rContext.m_pAccess = std::make_unique<BitmapScopedWriteAccess>(rBitmap); @@ -634,6 +647,40 @@ void GraphicFilter::ImportGraphics(std::vector< std::shared_ptr<Graphic> >& rGra else GraphicImportTask::doImport(rContext); } + else + rContext.m_nStatus = ERRCODE_GRFILTER_FILTERERROR; + } + else if (aFilterName.equalsIgnoreAsciiCase(IMP_PNG)) + { + rContext.m_eLinkType = GfxLinkType::NativePng; + + if (vcl::ImportPNG( *rContext.m_pStream, *rContext.m_pGraphic, rContext.m_nImportFlags | GraphicFilterImportFlags::OnlyCreateBitmap, nullptr, nullptr)) + { + const BitmapEx& rBitmapEx = rContext.m_pGraphic->GetBitmapExRef(); + Bitmap& rBitmap = const_cast<Bitmap&>(rBitmapEx.GetBitmap()); + rContext.m_pAccess = std::make_unique<BitmapScopedWriteAccess>(rBitmap); + // The png reader either uses only Bitmap or Bitmap+AlphaMask. + assert(rBitmapEx.IsAlpha() || !rBitmapEx.IsTransparent()); + if(rBitmapEx.IsAlpha()) + { + // The separate alpha bitmap causes a number of complications. Not only + // we need to have an extra bitmap access for it, but we also need + // to keep an AlphaMask instance in the context. This is because + // BitmapEx internally keeps Bitmap and not AlphaMask (because the Bitmap + // may be also a mask, not alpha). So BitmapEx::GetAlpha() returns + // a temporary, and direct access to the Bitmap wouldn't work + // with AlphaScopedBitmapAccess. *sigh* + rContext.mAlphaMask = rBitmapEx.GetAlpha(); + rContext.m_pAlphaAccess = std::make_unique<AlphaScopedWriteAccess>(rContext.mAlphaMask); + } + rContext.m_pStream->Seek(rContext.m_nStreamBegin); + if (bThreads) + rSharedPool.pushTask(std::make_unique<GraphicImportTask>(pTag, rContext)); + else + GraphicImportTask::doImport(rContext); + } + else + rContext.m_nStatus = ERRCODE_GRFILTER_FILTERERROR; } else rContext.m_nStatus = ERRCODE_GRFILTER_FILTERERROR; @@ -646,7 +693,10 @@ void GraphicFilter::ImportGraphics(std::vector< std::shared_ptr<Graphic> >& rGra // Process data after import. for (auto& rContext : aContexts) { + if(rContext.m_pAlphaAccess) // Need to move the AlphaMask back to the BitmapEx. + *rContext.m_pGraphic = BitmapEx( rContext.m_pGraphic->GetBitmapExRef().GetBitmap(), rContext.mAlphaMask ); rContext.m_pAccess.reset(); + rContext.m_pAlphaAccess.reset(); if (rContext.m_nStatus == ERRCODE_NONE && (rContext.m_eLinkType != GfxLinkType::NONE) && !rContext.m_pGraphic->GetReaderContext()) { @@ -686,16 +736,17 @@ void GraphicFilter::ImportGraphics(std::vector< std::shared_ptr<Graphic> >& rGra void GraphicFilter::MakeGraphicsAvailableThreaded(std::vector<Graphic*>& graphics) { - // Graphic::makeAvailable() is not thread-safe. Only the jpeg loader is, so here - // we process only jpeg images that also have their stream data, load new Graphic's + // Graphic::makeAvailable() is not thread-safe. Only the jpeg and png loaders are, so here + // we process only jpeg and png images that also have their stream data, load new Graphic's // from them and then update the passed objects using them. std::vector< Graphic* > toLoad; for(auto graphic : graphics) { // Need to use GetSharedGfxLink, to access the pointer without copying. if(!graphic->isAvailable() && graphic->IsGfxLink() - && graphic->GetSharedGfxLink()->GetType() == GfxLinkType::NativeJpg - && graphic->GetSharedGfxLink()->GetDataSize() != 0 ) + && graphic->GetSharedGfxLink()->GetDataSize() != 0 + && (graphic->GetSharedGfxLink()->GetType() == GfxLinkType::NativeJpg + || graphic->GetSharedGfxLink()->GetType() == GfxLinkType::NativePng)) { // Graphic objects share internal ImpGraphic, do not process any of those twice. const auto predicate = [graphic](Graphic* item) { return item->ImplGetImpGraphic() == graphic->ImplGetImpGraphic(); }; diff --git a/vcl/source/filter/png/PngImageReader.cxx b/vcl/source/filter/png/PngImageReader.cxx index b3a2265c23bf..898ef74d5836 100644 --- a/vcl/source/filter/png/PngImageReader.cxx +++ b/vcl/source/filter/png/PngImageReader.cxx @@ -21,6 +21,8 @@ #include <svdata.hxx> #include <salinst.hxx> +#include "png.hxx" + namespace { void lclReadStream(png_structp pPng, png_bytep pOutBytes, png_size_t nBytesToRead) @@ -65,7 +67,10 @@ struct PngDestructor png_infop pInfo; }; -bool reader(SvStream& rStream, BitmapEx& rBitmapEx, bool bUseBitmap32) +bool reader(SvStream& rStream, BitmapEx& rBitmapEx, + GraphicFilterImportFlags nImportFlags = GraphicFilterImportFlags::NONE, + BitmapScopedWriteAccess* pAccess = nullptr, + AlphaScopedWriteAccess* pAlphaAccess = nullptr) { if (!isPng(rStream)) return false; @@ -89,24 +94,33 @@ bool reader(SvStream& rStream, BitmapEx& rBitmapEx, bool bUseBitmap32) Bitmap aBitmap; AlphaMask aBitmapAlpha; Size prefSize; - BitmapScopedWriteAccess pWriteAccess; - AlphaScopedWriteAccess pWriteAccessAlpha; + BitmapScopedWriteAccess pWriteAccessInstance; + AlphaScopedWriteAccess pWriteAccessAlphaInstance; std::vector<std::vector<png_byte>> aRows; + auto pBackendCapabilities = ImplGetSVData()->mpDefInst->GetBackendCapabilities(); + const bool bSupportsBitmap32 = pBackendCapabilities->mbSupportsBitmap32; + const bool bOnlyCreateBitmap + = static_cast<bool>(nImportFlags & GraphicFilterImportFlags::OnlyCreateBitmap); + const bool bUseExistingBitmap + = static_cast<bool>(nImportFlags & GraphicFilterImportFlags::UseExistingBitmap); if (setjmp(png_jmpbuf(pPng))) { - // Set the bitmap if it contains something, even on failure. This allows - // reading images that are only partially broken. - pWriteAccess.reset(); - pWriteAccessAlpha.reset(); - if (!aBitmap.IsEmpty() && !aBitmapAlpha.IsEmpty()) - rBitmapEx = BitmapEx(aBitmap, aBitmapAlpha); - else if (!aBitmap.IsEmpty()) - rBitmapEx = BitmapEx(aBitmap); - if (!rBitmapEx.IsEmpty() && !prefSize.IsEmpty()) + if (!bUseExistingBitmap) { - rBitmapEx.SetPrefMapMode(MapMode(MapUnit::Map100thMM)); - rBitmapEx.SetPrefSize(prefSize); + // Set the bitmap if it contains something, even on failure. This allows + // reading images that are only partially broken. + pWriteAccessInstance.reset(); + pWriteAccessAlphaInstance.reset(); + if (!aBitmap.IsEmpty() && !aBitmapAlpha.IsEmpty()) + rBitmapEx = BitmapEx(aBitmap, aBitmapAlpha); + else if (!aBitmap.IsEmpty()) + rBitmapEx = BitmapEx(aBitmap); + if (!rBitmapEx.IsEmpty() && !prefSize.IsEmpty()) + { + rBitmapEx.SetPrefMapMode(MapMode(MapUnit::Map100thMM)); + rBitmapEx.SetPrefSize(prefSize); + } } return false; } @@ -188,12 +202,60 @@ bool reader(SvStream& rStream, BitmapEx& rBitmapEx, bool bUseBitmap32) static_cast<sal_Int32>((100000.0 * height) / res_y)); } - if (colorType == PNG_COLOR_TYPE_RGB) + if (!bUseExistingBitmap) { - aBitmap = Bitmap(Size(width, height), vcl::PixelFormat::N24_BPP); - pWriteAccess = BitmapScopedWriteAccess(aBitmap); - if (!pWriteAccess) + switch (colorType) + { + case PNG_COLOR_TYPE_RGB: + aBitmap = Bitmap(Size(width, height), vcl::PixelFormat::N24_BPP); + break; + case PNG_COLOR_TYPE_RGBA: + if (bSupportsBitmap32) + aBitmap = Bitmap(Size(width, height), vcl::PixelFormat::N32_BPP); + else + { + aBitmap = Bitmap(Size(width, height), vcl::PixelFormat::N24_BPP); + aBitmapAlpha = AlphaMask(Size(width, height), nullptr); + } + break; + case PNG_COLOR_TYPE_GRAY: + aBitmap = Bitmap(Size(width, height), vcl::PixelFormat::N8_BPP, + &Bitmap::GetGreyPalette(256)); + break; + default: + abort(); + } + + if (bOnlyCreateBitmap) + { + if (!aBitmapAlpha.IsEmpty()) + rBitmapEx = BitmapEx(aBitmap, aBitmapAlpha); + else + rBitmapEx = BitmapEx(aBitmap); + if (!prefSize.IsEmpty()) + { + rBitmapEx.SetPrefMapMode(MapMode(MapUnit::Map100thMM)); + rBitmapEx.SetPrefSize(prefSize); + } + return true; + } + + pWriteAccessInstance = BitmapScopedWriteAccess(aBitmap); + if (!pWriteAccessInstance) return false; + if (!aBitmapAlpha.IsEmpty()) + { + pWriteAccessAlphaInstance = AlphaScopedWriteAccess(aBitmapAlpha); + if (!pWriteAccessAlphaInstance) + return false; + } + } + BitmapScopedWriteAccess& pWriteAccess = pAccess ? *pAccess : pWriteAccessInstance; + AlphaScopedWriteAccess& pWriteAccessAlpha + = pAlphaAccess ? *pAlphaAccess : pWriteAccessAlphaInstance; + + if (colorType == PNG_COLOR_TYPE_RGB) + { ScanlineFormat eFormat = pWriteAccess->GetScanlineFormat(); if (eFormat == ScanlineFormat::N24BitTcBgr) png_set_bgr(pPng); @@ -206,24 +268,16 @@ bool reader(SvStream& rStream, BitmapEx& rBitmapEx, bool bUseBitmap32) png_read_row(pPng, pScanline, nullptr); } } - pWriteAccess.reset(); - rBitmapEx = BitmapEx(aBitmap); } else if (colorType == PNG_COLOR_TYPE_RGB_ALPHA) { size_t aRowSizeBytes = png_get_rowbytes(pPng, pInfo); - if (bUseBitmap32) + if (bSupportsBitmap32) { - aBitmap = Bitmap(Size(width, height), vcl::PixelFormat::N32_BPP); - pWriteAccess = BitmapScopedWriteAccess(aBitmap); - if (!pWriteAccess) - return false; ScanlineFormat eFormat = pWriteAccess->GetScanlineFormat(); if (eFormat == ScanlineFormat::N32BitTcAbgr || eFormat == ScanlineFormat::N32BitTcBgra) - { png_set_bgr(pPng); - } for (int pass = 0; pass < nNumberOfPasses; pass++) { @@ -263,22 +317,13 @@ bool reader(SvStream& rStream, BitmapEx& rBitmapEx, bool bUseBitmap32) } } } - pWriteAccess.reset(); - rBitmapEx = BitmapEx(aBitmap); } else { - aBitmap = Bitmap(Size(width, height), vcl::PixelFormat::N24_BPP); - aBitmapAlpha = AlphaMask(Size(width, height), nullptr); - pWriteAccess = BitmapScopedWriteAccess(aBitmap); - if (!pWriteAccess) - return false; ScanlineFormat eFormat = pWriteAccess->GetScanlineFormat(); if (eFormat == ScanlineFormat::N24BitTcBgr) png_set_bgr(pPng); - pWriteAccessAlpha = AlphaScopedWriteAccess(aBitmapAlpha); - aRows = std::vector<std::vector<png_byte>>(height); for (auto& rRow : aRows) rRow.resize(aRowSizeBytes, 0); @@ -302,20 +347,11 @@ bool reader(SvStream& rStream, BitmapEx& rBitmapEx, bool bUseBitmap32) } } } - pWriteAccess.reset(); - pWriteAccessAlpha.reset(); - rBitmapEx = BitmapEx(aBitmap, aBitmapAlpha); } } else if (colorType == PNG_COLOR_TYPE_GRAY) { - aBitmap - = Bitmap(Size(width, height), vcl::PixelFormat::N8_BPP, &Bitmap::GetGreyPalette(256)); aBitmap.Erase(COL_WHITE); - pWriteAccess = BitmapScopedWriteAccess(aBitmap); - if (!pWriteAccess) - return false; - for (int pass = 0; pass < nNumberOfPasses; pass++) { for (png_uint_32 y = 0; y < height; y++) @@ -324,16 +360,23 @@ bool reader(SvStream& rStream, BitmapEx& rBitmapEx, bool bUseBitmap32) png_read_row(pPng, pScanline, nullptr); } } - pWriteAccess.reset(); - rBitmapEx = BitmapEx(aBitmap); } png_read_end(pPng, pInfo); - if (!prefSize.IsEmpty()) + if (!bUseExistingBitmap) { - rBitmapEx.SetPrefMapMode(MapMode(MapUnit::Map100thMM)); - rBitmapEx.SetPrefSize(prefSize); + pWriteAccess.reset(); + pWriteAccessAlpha.reset(); + if (!aBitmapAlpha.IsEmpty()) + rBitmapEx = BitmapEx(aBitmap, aBitmapAlpha); + else + rBitmapEx = BitmapEx(aBitmap); + if (!prefSize.IsEmpty()) + { + rBitmapEx.SetPrefMapMode(MapMode(MapUnit::Map100thMM)); + rBitmapEx.SetPrefSize(prefSize); + } } return true; @@ -407,13 +450,7 @@ PngImageReader::PngImageReader(SvStream& rStream) { } -bool PngImageReader::read(BitmapEx& rBitmapEx) -{ - auto pBackendCapabilities = ImplGetSVData()->mpDefInst->GetBackendCapabilities(); - bool bSupportsBitmap32 = pBackendCapabilities->mbSupportsBitmap32; - - return reader(mrStream, rBitmapEx, bSupportsBitmap32); -} +bool PngImageReader::read(BitmapEx& rBitmapEx) { return reader(mrStream, rBitmapEx); } BitmapEx PngImageReader::read() { @@ -434,6 +471,19 @@ std::unique_ptr<sal_uInt8[]> PngImageReader::getMicrosoftGifChunk(SvStream& rStr return chunk; } +bool ImportPNG(SvStream& rInputStream, Graphic& rGraphic, GraphicFilterImportFlags nImportFlags, + BitmapScopedWriteAccess* pAccess, AlphaScopedWriteAccess* pAlphaAccess) +{ + // Creating empty bitmaps should be practically a no-op, and thus thread-safe. + BitmapEx bitmap; + if (reader(rInputStream, bitmap, nImportFlags, pAccess, pAlphaAccess)) + { + rGraphic = bitmap; + return true; + } + return false; +} + } // namespace vcl /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/filter/png/png.hxx b/vcl/source/filter/png/png.hxx new file mode 100644 index 000000000000..b3a1b7b17b1a --- /dev/null +++ b/vcl/source/filter/png/png.hxx @@ -0,0 +1,33 @@ +/* -*- 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> +#include <vcl/graphicfilter.hxx> + +#include <bitmap/BitmapWriteAccess.hxx> + +namespace vcl +{ +bool ImportPNG(SvStream& rInputStream, Graphic& rGraphic, GraphicFilterImportFlags nImportFlags, + BitmapScopedWriteAccess* pAccess, AlphaScopedWriteAccess* pAlphaAccess); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |