summaryrefslogtreecommitdiffstats
path: root/vcl/source/image
diff options
context:
space:
mode:
Diffstat (limited to 'vcl/source/image')
-rw-r--r--vcl/source/image/Image.cxx165
-rw-r--r--vcl/source/image/ImageRepository.cxx37
-rw-r--r--vcl/source/image/ImageTree.cxx67
-rw-r--r--vcl/source/image/ImplImage.cxx186
-rw-r--r--vcl/source/image/ImplImageTree.cxx716
5 files changed, 1171 insertions, 0 deletions
diff --git a/vcl/source/image/Image.cxx b/vcl/source/image/Image.cxx
new file mode 100644
index 0000000000..f8c0d56f88
--- /dev/null
+++ b/vcl/source/image/Image.cxx
@@ -0,0 +1,165 @@
+/* -*- 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 <vcl/settings.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/graph.hxx>
+#include <vcl/graphicfilter.hxx>
+#include <vcl/image.hxx>
+#include <sal/types.h>
+#include <image.h>
+
+#include <bitmap/BitmapColorizeFilter.hxx>
+
+using namespace css;
+
+Image::Image()
+{
+}
+
+Image::Image(const BitmapEx& rBitmapEx)
+{
+ ImplInit(rBitmapEx);
+}
+
+Image::Image(uno::Reference<graphic::XGraphic> const & rxGraphic)
+{
+ if (rxGraphic.is())
+ {
+ const Graphic aGraphic(rxGraphic);
+
+ OUString aPath;
+ if (aGraphic.getOriginURL().startsWith("private:graphicrepository/", &aPath))
+ mpImplData = std::make_shared<ImplImage>(aPath);
+ else if (aGraphic.GetType() == GraphicType::GdiMetafile)
+ mpImplData = std::make_shared<ImplImage>(aGraphic.GetGDIMetaFile());
+ else
+ ImplInit(aGraphic.GetBitmapEx());
+ }
+}
+
+Image::Image(const OUString & rFileUrl)
+{
+ OUString sImageName;
+ if (rFileUrl.startsWith("private:graphicrepository/", &sImageName))
+ mpImplData = std::make_shared<ImplImage>(sImageName);
+ else
+ {
+ Graphic aGraphic;
+ if (ERRCODE_NONE == GraphicFilter::LoadGraphic(rFileUrl, IMP_PNG, aGraphic))
+ ImplInit(aGraphic.GetBitmapEx());
+ }
+}
+
+Image::Image(StockImage, const OUString & rFileUrl)
+ : mpImplData(std::make_shared<ImplImage>(rFileUrl))
+{
+}
+
+void Image::ImplInit(const BitmapEx& rBitmapEx)
+{
+ if (!rBitmapEx.IsEmpty())
+ mpImplData = std::make_shared<ImplImage>(rBitmapEx);
+}
+
+OUString Image::GetStock() const
+{
+ if (mpImplData)
+ return mpImplData->getStock();
+ return OUString();
+}
+
+Size Image::GetSizePixel() const
+{
+ if (mpImplData)
+ return mpImplData->getSizePixel();
+ else
+ return Size();
+}
+
+BitmapEx Image::GetBitmapEx() const
+{
+ if (mpImplData)
+ return mpImplData->getBitmapEx();
+ else
+ return BitmapEx();
+}
+
+bool Image::operator==(const Image& rImage) const
+{
+ bool bRet = false;
+
+ if (rImage.mpImplData == mpImplData)
+ bRet = true;
+ else if (!rImage.mpImplData || !mpImplData)
+ bRet = false;
+ else
+ bRet = rImage.mpImplData->isEqual(*mpImplData);
+
+ return bRet;
+}
+
+void Image::Draw(OutputDevice* pOutDev, const Point& rPos, DrawImageFlags nStyle, const Size* pSize)
+{
+ if (!mpImplData || (!pOutDev->IsDeviceOutputNecessary() && pOutDev->GetConnectMetaFile() == nullptr))
+ return;
+
+ Size aOutSize = pSize ? *pSize : pOutDev->PixelToLogic(mpImplData->getSizePixel());
+
+ BitmapEx aRenderBmp = mpImplData->getBitmapExForHiDPI(bool(nStyle & DrawImageFlags::Disable), pOutDev->GetGraphics());
+
+ if (!(nStyle & DrawImageFlags::Disable) &&
+ (nStyle & (DrawImageFlags::ColorTransform | DrawImageFlags::Highlight |
+ DrawImageFlags::Deactive | DrawImageFlags::SemiTransparent)))
+ {
+ BitmapEx aTempBitmapEx(aRenderBmp);
+
+ if (nStyle & (DrawImageFlags::Highlight | DrawImageFlags::Deactive))
+ {
+ const StyleSettings& rSettings = pOutDev->GetSettings().GetStyleSettings();
+ Color aColor;
+ if (nStyle & DrawImageFlags::Highlight)
+ aColor = rSettings.GetHighlightColor();
+ else
+ aColor = rSettings.GetDeactiveColor();
+
+ BitmapFilter::Filter(aTempBitmapEx, BitmapColorizeFilter(aColor));
+ }
+
+ if (nStyle & DrawImageFlags::SemiTransparent)
+ {
+ if (aTempBitmapEx.IsAlpha())
+ {
+ Bitmap aAlphaBmp(aTempBitmapEx.GetAlphaMask().GetBitmap());
+ aAlphaBmp.Adjust(50);
+ aTempBitmapEx = BitmapEx(aTempBitmapEx.GetBitmap(), AlphaMask(aAlphaBmp));
+ }
+ else
+ {
+ sal_uInt8 cErase = 128;
+ aTempBitmapEx = BitmapEx(aTempBitmapEx.GetBitmap(), AlphaMask(aTempBitmapEx.GetSizePixel(), &cErase));
+ }
+ }
+ aRenderBmp = aTempBitmapEx;
+ }
+
+ pOutDev->DrawBitmapEx(rPos, aOutSize, aRenderBmp);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/image/ImageRepository.cxx b/vcl/source/image/ImageRepository.cxx
new file mode 100644
index 0000000000..6bf57e6994
--- /dev/null
+++ b/vcl/source/image/ImageRepository.cxx
@@ -0,0 +1,37 @@
+/* -*- 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 <vcl/bitmapex.hxx>
+#include <imagerepository.hxx>
+#include <vcl/ImageTree.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/svapp.hxx>
+
+namespace vcl
+{
+ bool ImageRepository::loadImage( const OUString& _rName, BitmapEx& _out_rImage )
+ {
+ OUString sIconTheme = Application::GetSettings().GetStyleSettings().DetermineIconTheme();
+
+ return ImageTree::get().loadImage( _rName, sIconTheme, _out_rImage, false/*_bSearchLanguageDependent*/ );
+ }
+
+} // namespace vcl
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/image/ImageTree.cxx b/vcl/source/image/ImageTree.cxx
new file mode 100644
index 0000000000..fdc47cbfe7
--- /dev/null
+++ b/vcl/source/image/ImageTree.cxx
@@ -0,0 +1,67 @@
+/* -*- 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 <vcl/ImageTree.hxx>
+#include <implimagetree.hxx>
+#include <com/sun/star/uno/Reference.hxx>
+#include <com/sun/star/container/XNameAccess.hpp>
+
+ImageTree & ImageTree::get() {
+ static ImageTree s_ImageTree;
+ return s_ImageTree;
+}
+
+ImageTree::ImageTree()
+ : mpImplImageTree(new ImplImageTree)
+{
+}
+
+OUString ImageTree::getImageUrl(OUString const & rName, OUString const & rStyle, OUString const & rLang)
+
+{
+ return mpImplImageTree->getImageUrl(rName, rStyle, rLang);
+}
+
+std::shared_ptr<SvMemoryStream> ImageTree::getImageStream(OUString const & rName, OUString const & rStyle, OUString const & rLang)
+{
+ return mpImplImageTree->getImageStream(rName, rStyle, rLang);
+}
+
+css::uno::Reference<css::io::XInputStream> ImageTree::getImageXInputStream(OUString const & rName, OUString const & rStyle, OUString const & rLang)
+{
+ return mpImplImageTree->getImageXInputStream(rName, rStyle, rLang);
+}
+
+bool ImageTree::loadImage(OUString const & rName, OUString const & rStyle,
+ BitmapEx & rBitmap, bool bLocalized,
+ sal_Int32 nScalePercentage,
+ const ImageLoadFlags eFlags)
+{
+ return mpImplImageTree->loadImage(rName, rStyle, rBitmap, bLocalized, eFlags, nScalePercentage);
+}
+
+bool ImageTree::loadImage(OUString const & rName, OUString const & rStyle,
+ BitmapEx & rBitmap, bool bLocalized,
+ const ImageLoadFlags eFlags)
+{
+ return loadImage(rName, rStyle, rBitmap, bLocalized, -1, eFlags);
+}
+
+css::uno::Reference<css::container::XNameAccess> const & ImageTree::getNameAccess()
+{
+ return mpImplImageTree->getNameAccess();
+}
+
+void ImageTree::shutdown()
+{
+ mpImplImageTree->shutdown();
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/image/ImplImage.cxx b/vcl/source/image/ImplImage.cxx
new file mode 100644
index 0000000000..cf70052a9f
--- /dev/null
+++ b/vcl/source/image/ImplImage.cxx
@@ -0,0 +1,186 @@
+/* -*- 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 <sal/log.hxx>
+#include <utility>
+#include <vcl/svapp.hxx>
+#include <vcl/bitmapex.hxx>
+#include <vcl/gdimtf.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/virdev.hxx>
+#include <vcl/BitmapFilter.hxx>
+#include <vcl/ImageTree.hxx>
+#include <vcl/skia/SkiaHelper.hxx>
+#include <bitmap/BitmapDisabledImageFilter.hxx>
+#include <comphelper/lok.hxx>
+
+#include <image.h>
+#include <salgdi.hxx>
+
+ImplImage::ImplImage(const BitmapEx &rBitmapEx)
+ : maBitmapChecksum(0)
+ , maSizePixel(rBitmapEx.GetSizePixel())
+ , maBitmapEx(rBitmapEx)
+{
+}
+
+ImplImage::ImplImage(OUString aStockName)
+ : maBitmapChecksum(0)
+ , maStockName(std::move(aStockName))
+{
+}
+
+ImplImage::ImplImage(const GDIMetaFile& rMetaFile)
+ : maBitmapChecksum(0)
+ , maSizePixel(rMetaFile.GetPrefSize())
+ , mxMetaFile(new GDIMetaFile(rMetaFile))
+{
+}
+
+bool ImplImage::loadStockAtScale(SalGraphics* pGraphics, BitmapEx &rBitmapEx)
+{
+ BitmapEx aBitmapEx;
+
+ ImageLoadFlags eScalingFlags = ImageLoadFlags::NONE;
+ sal_Int32 nScalePercentage = -1;
+
+ double fScale(1.0);
+ if (pGraphics && pGraphics->ShouldDownscaleIconsAtSurface(&fScale)) // scale at the surface
+ {
+ nScalePercentage = fScale * 100.0;
+ eScalingFlags = ImageLoadFlags::IgnoreScalingFactor;
+ }
+
+ OUString aIconTheme = Application::GetSettings().GetStyleSettings().DetermineIconTheme();
+ if (!ImageTree::get().loadImage(maStockName, aIconTheme, aBitmapEx, true,
+ nScalePercentage, eScalingFlags))
+ {
+ /* If the uno command has parameters, passed in from a toolbar,
+ * recover from failure by removing the parameters from the file name
+ */
+ if (maStockName.indexOf("%3f") > 0)
+ {
+ sal_Int32 nStart = maStockName.indexOf("%3f");
+ sal_Int32 nEnd = maStockName.lastIndexOf(".");
+
+ OUString aFileName = maStockName.replaceAt(nStart, nEnd - nStart, u"");
+ if (!ImageTree::get().loadImage(aFileName, aIconTheme, aBitmapEx, true,
+ nScalePercentage, eScalingFlags))
+ {
+ SAL_WARN("vcl", "Failed to load scaled image from " << maStockName <<
+ " and " << aFileName << " at " << fScale);
+ return false;
+ }
+ }
+ else
+ {
+ SAL_WARN("vcl", "Failed to load scaled image from " << maStockName <<
+ " at " << fScale);
+ return false;
+ }
+ }
+ rBitmapEx = aBitmapEx;
+ return true;
+}
+
+Size ImplImage::getSizePixel()
+{
+ Size aRet;
+ if (!isSizeEmpty())
+ aRet = maSizePixel;
+ else if (isStock())
+ {
+ if (loadStockAtScale(nullptr, maBitmapEx))
+ {
+ assert(maDisabledBitmapEx.IsEmpty());
+ assert(maBitmapChecksum == 0);
+ maSizePixel = maBitmapEx.GetSizePixel();
+ aRet = maSizePixel;
+ }
+ else
+ SAL_WARN("vcl", "Failed to load stock icon " << maStockName);
+ }
+ return aRet;
+}
+
+/// non-HiDPI compatibility method.
+BitmapEx const & ImplImage::getBitmapEx(bool bDisabled)
+{
+ getSizePixel(); // force load, and at unity scale.
+ if (bDisabled)
+ {
+ // Changed since we last generated this.
+ BitmapChecksum aChecksum = maBitmapEx.GetChecksum();
+ if (maBitmapChecksum != aChecksum ||
+ maDisabledBitmapEx.GetSizePixel() != maBitmapEx.GetSizePixel())
+ {
+ maDisabledBitmapEx = maBitmapEx;
+ BitmapFilter::Filter(maDisabledBitmapEx, BitmapDisabledImageFilter());
+ maBitmapChecksum = aChecksum;
+ }
+ return maDisabledBitmapEx;
+ }
+
+ return maBitmapEx;
+}
+
+bool ImplImage::isEqual(const ImplImage &ref) const
+{
+ if (isStock() != ref.isStock())
+ return false;
+ if (isStock())
+ return maStockName == ref.maStockName;
+ else
+ return maBitmapEx == ref.maBitmapEx;
+}
+
+BitmapEx const & ImplImage::getBitmapExForHiDPI(bool bDisabled, SalGraphics* pGraphics)
+{
+ if ((isStock() || mxMetaFile) && pGraphics)
+ { // check we have the right bitmap cached.
+ double fScale = 1.0;
+ pGraphics->ShouldDownscaleIconsAtSurface(&fScale);
+ Size aTarget(maSizePixel.Width()*fScale,
+ maSizePixel.Height()*fScale);
+ if (maBitmapEx.GetSizePixel() != aTarget)
+ {
+ if (isStock())
+ loadStockAtScale(pGraphics, maBitmapEx);
+ else // if (mxMetaFile)
+ {
+ ScopedVclPtrInstance<VirtualDevice> aVDev(DeviceFormat::WITH_ALPHA);
+
+ // Fix white background in font color and font background color
+ // in the Breeze icons by setting the alpha mask to transparent
+ bool bAlphaMaskTransparent = true;
+#if HAVE_FEATURE_SKIA
+ if (SkiaHelper::isVCLSkiaEnabled() && SkiaHelper::isAlphaMaskBlendingEnabled())
+ bAlphaMaskTransparent = false;
+#endif
+ aVDev->SetOutputSizePixel(aTarget, true, bAlphaMaskTransparent);
+ mxMetaFile->WindStart();
+ mxMetaFile->Play(*aVDev, Point(), aTarget);
+ maBitmapEx = aVDev->GetBitmapEx(Point(), aTarget);
+ }
+ }
+ }
+ return getBitmapEx(bDisabled);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/source/image/ImplImageTree.cxx b/vcl/source/image/ImplImageTree.cxx
new file mode 100644
index 0000000000..94ce3b94cf
--- /dev/null
+++ b/vcl/source/image/ImplImageTree.cxx
@@ -0,0 +1,716 @@
+/* -*- 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 <config_folders.h>
+
+#include <sal/config.h>
+#include <sal/log.hxx>
+
+#include <deque>
+#include <string_view>
+
+#include <com/sun/star/container/XNameAccess.hpp>
+#include <com/sun/star/io/XInputStream.hpp>
+#include <com/sun/star/packages/zip/ZipFileAccess.hpp>
+#include <com/sun/star/ucb/SimpleFileAccess.hpp>
+#include <com/sun/star/uno/Exception.hpp>
+#include <com/sun/star/uno/RuntimeException.hpp>
+#include <com/sun/star/uno/Sequence.hxx>
+#include <comphelper/processfactory.hxx>
+#include <cppuhelper/implbase.hxx>
+#include <osl/file.hxx>
+#include <osl/diagnose.h>
+#include <osl/process.h>
+#include <rtl/bootstrap.hxx>
+#include <rtl/uri.hxx>
+#include <rtl/strbuf.hxx>
+
+#include <comphelper/diagnose_ex.hxx>
+#include <tools/stream.hxx>
+#include <tools/urlobj.hxx>
+#include <implimagetree.hxx>
+
+#include <utility>
+#include <vcl/bitmapex.hxx>
+#include <vcl/dibtools.hxx>
+#include <vcl/settings.hxx>
+#include <vcl/svapp.hxx>
+#include <vcl/BitmapTools.hxx>
+#include <IconThemeScanner.hxx>
+#include <vcl/filter/PngImageReader.hxx>
+#include <vcl/outdev.hxx>
+#include <vcl/filter/PngImageWriter.hxx>
+#include <o3tl/string_view.hxx>
+#include <bitmap/BitmapLightenFilter.hxx>
+
+using namespace css;
+
+bool ImageRequestParameters::convertToDarkTheme()
+{
+ static bool bIconsForDarkTheme = !!getenv("VCL_ICONS_FOR_DARK_THEME");
+
+ bool bConvertToDarkTheme = false;
+ if (!(meFlags & ImageLoadFlags::IgnoreDarkTheme))
+ bConvertToDarkTheme = bIconsForDarkTheme;
+
+ return bConvertToDarkTheme;
+}
+
+sal_Int32 ImageRequestParameters::scalePercentage()
+{
+ sal_Int32 aScalePercentage = 100;
+ OutputDevice* pDevice = Application::GetDefaultDevice();
+ if (!(meFlags & ImageLoadFlags::IgnoreScalingFactor) && pDevice != nullptr)
+ aScalePercentage = pDevice->GetDPIScalePercentage();
+ else if (mnScalePercentage > 0)
+ aScalePercentage = mnScalePercentage;
+ return aScalePercentage;
+}
+
+namespace
+{
+
+OUString convertLcTo32Path(std::u16string_view rPath)
+{
+ OUString aResult;
+ size_t nSlashPos = rPath.rfind('/');
+ if (nSlashPos != std::u16string_view::npos)
+ {
+ sal_Int32 nCopyFrom = nSlashPos + 1;
+ std::u16string_view sFile = rPath.substr(nCopyFrom);
+ std::u16string_view sDir = rPath.substr(0, nSlashPos);
+ if (!sFile.empty() && o3tl::starts_with(sFile, u"lc_"))
+ {
+ aResult = OUString::Concat(sDir) + "/32/" + sFile.substr(3);
+ }
+ }
+ return aResult;
+}
+
+OUString createPath(std::u16string_view name, sal_Int32 pos, std::u16string_view locale)
+{
+ return OUString::Concat(name.substr(0, pos + 1)) + locale + name.substr(pos);
+}
+
+OUString getIconCacheUrl(std::u16string_view sVariant, ImageRequestParameters const & rParameters)
+{
+ // the macro expansion can be expensive in bulk, so cache that
+ static OUString CACHE_DIR = []()
+ {
+ OUString sDir = "${$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("bootstrap") ":UserInstallation}/cache/";
+ rtl::Bootstrap::expandMacros(sDir);
+ return sDir;
+ }();
+ return CACHE_DIR + rParameters.msStyle + "/" + sVariant + "/" + rParameters.msName;
+}
+
+OUString createIconCacheUrl(
+ std::u16string_view sVariant, ImageRequestParameters const & rParameters)
+{
+ OUString sUrl(getIconCacheUrl(sVariant, rParameters));
+ OUString sDir = sUrl.copy(0, sUrl.lastIndexOf('/'));
+ osl::Directory::createPath(sDir);
+ return sUrl;
+}
+
+bool urlExists(OUString const & sUrl)
+{
+ osl::File aFile(sUrl);
+ osl::FileBase::RC eRC = aFile.open(osl_File_OpenFlag_Read);
+ return osl::FileBase::E_None == eRC;
+}
+
+OUString getNameNoExtension(std::u16string_view sName)
+{
+ size_t nDotPosition = sName.rfind('.');
+ return OUString(sName.substr(0, nDotPosition));
+}
+
+std::shared_ptr<SvMemoryStream> wrapStream(uno::Reference<io::XInputStream> const & rInputStream)
+{
+ // This could use SvInputStream instead if that did not have a broken
+ // SeekPos implementation for an XInputStream that is not also XSeekable
+ // (cf. "@@@" at tags/DEV300_m37/svtools/source/misc1/strmadpt.cxx@264807
+ // l. 593):
+ OSL_ASSERT(rInputStream.is());
+ std::shared_ptr<SvMemoryStream> aMemoryStream(std::make_shared<SvMemoryStream>());
+ for (;;)
+ {
+ const sal_Int32 nSize(2048);
+ uno::Sequence<sal_Int8> aData(nSize);
+ sal_Int32 nRead = rInputStream->readBytes(aData, nSize);
+ aMemoryStream->WriteBytes(aData.getConstArray(), nRead);
+ if (nRead < nSize)
+ break;
+ }
+ aMemoryStream->Seek(0);
+ rInputStream->closeInput();
+ return aMemoryStream;
+}
+
+void loadImageFromStream(std::shared_ptr<SvStream> const & xStream, OUString const & rPath, ImageRequestParameters& rParameters)
+{
+ bool bConvertToDarkTheme = rParameters.convertToDarkTheme();
+ sal_Int32 aScalePercentage = rParameters.scalePercentage();
+
+ if (rPath.endsWith(".png"))
+ {
+ vcl::PngImageReader aPNGReader(*xStream);
+ aPNGReader.read(rParameters.mrBitmap);
+ }
+ else if (rPath.endsWith(".svg"))
+ {
+ rParameters.mbWriteImageToCache = true; // We always want to cache a SVG image
+ vcl::bitmap::loadFromSvg(*xStream, rPath, rParameters.mrBitmap, aScalePercentage / 100.0);
+
+ if (bConvertToDarkTheme)
+ BitmapFilter::Filter(rParameters.mrBitmap, BitmapLightenFilter());
+
+ return;
+ }
+ else
+ {
+ ReadDIBBitmapEx(rParameters.mrBitmap, *xStream);
+ }
+
+ if (bConvertToDarkTheme)
+ {
+ rParameters.mbWriteImageToCache = true; // Cache the dark variant
+ BitmapFilter::Filter(rParameters.mrBitmap, BitmapLightenFilter());
+ }
+
+ if (aScalePercentage > 100)
+ {
+ rParameters.mbWriteImageToCache = true; // Cache the scaled variant
+ double aScaleFactor(aScalePercentage / 100.0);
+ // when scaling use the full 24bit RGB values
+ rParameters.mrBitmap.Convert(BmpConversion::N24Bit);
+ rParameters.mrBitmap.Scale(aScaleFactor, aScaleFactor, BmpScaleFlag::Fast);
+ }
+}
+
+} // end anonymous namespace
+
+ImplImageTree::ImplImageTree()
+{
+}
+
+ImplImageTree::~ImplImageTree()
+{
+}
+
+std::vector<OUString> ImplImageTree::getPaths(OUString const & name, LanguageTag const & rLanguageTag)
+{
+ std::vector<OUString> sPaths;
+
+ sal_Int32 pos = name.lastIndexOf('/');
+ if (pos != -1)
+ {
+ for (const OUString& rFallback : rLanguageTag.getFallbackStrings(true))
+ {
+ OUString aFallbackName = getNameNoExtension(getRealImageName(createPath(name, pos, rFallback)));
+ sPaths.emplace_back(aFallbackName + ".png");
+ sPaths.emplace_back(aFallbackName + ".svg");
+ }
+ }
+
+ OUString aRealName = getNameNoExtension(getRealImageName(name));
+ sPaths.emplace_back(aRealName + ".png");
+ sPaths.emplace_back(aRealName + ".svg");
+
+ return sPaths;
+}
+
+OUString ImplImageTree::getImageUrl(OUString const & rName, OUString const & rStyle, OUString const & rLang)
+{
+ OUString aStyle(rStyle);
+
+ while (!aStyle.isEmpty())
+ {
+ try
+ {
+ setStyle(aStyle);
+
+ if (checkPathAccess())
+ {
+ IconSet& rIconSet = getCurrentIconSet();
+ const uno::Reference<container::XNameAccess> & rNameAccess = rIconSet.maNameAccess;
+
+ LanguageTag aLanguageTag(rLang);
+
+ for (const OUString& rPath: getPaths(rName, aLanguageTag))
+ {
+ if (rNameAccess->hasByName(rPath))
+ {
+ return "vnd.sun.star.zip://"
+ + rtl::Uri::encode(rIconSet.maURL, rtl_UriCharClassRegName,
+ rtl_UriEncodeIgnoreEscapes, RTL_TEXTENCODING_UTF8)
+ + "/" + rPath;
+ }
+ }
+ }
+ }
+ catch (const uno::Exception &)
+ {
+ TOOLS_INFO_EXCEPTION("vcl", "");
+ }
+
+ aStyle = fallbackStyle(aStyle);
+ }
+ return OUString();
+}
+
+uno::Reference<io::XInputStream> ImplImageTree::getImageXInputStream(OUString const & rName, OUString const & rStyle, OUString const & rLang)
+{
+ OUString aStyle(rStyle);
+
+ while (!aStyle.isEmpty())
+ {
+ try
+ {
+ setStyle(aStyle);
+
+ if (checkPathAccess())
+ {
+ IconSet& rIconSet = getCurrentIconSet();
+ const uno::Reference<container::XNameAccess>& rNameAccess = rIconSet.maNameAccess;
+
+ LanguageTag aLanguageTag(rLang);
+
+ for (const OUString& rPath: getPaths(rName, aLanguageTag))
+ {
+ if (rNameAccess->hasByName(rPath))
+ {
+ uno::Reference<io::XInputStream> aStream;
+ bool ok = rNameAccess->getByName(rPath) >>= aStream;
+ assert(ok);
+ (void)ok; // prevent unused warning in release build
+ return aStream;
+ }
+ }
+ }
+ }
+ catch (const uno::Exception &)
+ {
+ TOOLS_INFO_EXCEPTION("vcl", "");
+ }
+
+ aStyle = fallbackStyle(aStyle);
+ }
+ return nullptr;
+}
+
+std::shared_ptr<SvMemoryStream> ImplImageTree::getImageStream(OUString const & rName, OUString const & rStyle, OUString const & rLang)
+{
+ uno::Reference<io::XInputStream> xStream = getImageXInputStream(rName, rStyle, rLang);
+ if (xStream)
+ return wrapStream(xStream);
+ return std::shared_ptr<SvMemoryStream>();
+}
+
+OUString ImplImageTree::fallbackStyle(std::u16string_view rsStyle)
+{
+ OUString sResult;
+
+ if (rsStyle == u"colibre" || rsStyle == u"helpimg")
+ sResult = "";
+ else if (rsStyle == u"sifr" || rsStyle == u"breeze_dark")
+ sResult = "breeze";
+ else if (rsStyle == u"sifr_dark" )
+ sResult = "breeze_dark";
+ else
+ sResult = "colibre";
+
+ return sResult;
+}
+
+bool ImplImageTree::loadImage(OUString const & rName, OUString const & rStyle, BitmapEx & rBitmap, bool localized,
+ const ImageLoadFlags eFlags, sal_Int32 nScalePercentage)
+{
+ OUString aCurrentStyle(rStyle);
+ while (!aCurrentStyle.isEmpty())
+ {
+ try
+ {
+ ImageRequestParameters aParameters(rName, aCurrentStyle, rBitmap, localized, eFlags, nScalePercentage);
+ if (doLoadImage(aParameters))
+ return true;
+ }
+ catch (uno::RuntimeException &)
+ {}
+
+ aCurrentStyle = fallbackStyle(aCurrentStyle);
+ }
+ return false;
+}
+
+namespace
+{
+
+OUString createVariant(ImageRequestParameters& rParameters)
+{
+ bool bConvertToDarkTheme = rParameters.convertToDarkTheme();
+ sal_Int32 aScalePercentage = rParameters.scalePercentage();
+
+ OUString aVariant = OUString::number(aScalePercentage);
+
+ if (bConvertToDarkTheme)
+ aVariant += "-dark";
+
+ return aVariant;
+}
+
+bool loadDiskCachedVersion(std::u16string_view sVariant, ImageRequestParameters& rParameters)
+{
+ OUString sUrl(getIconCacheUrl(sVariant, rParameters));
+ if (!urlExists(sUrl))
+ return false;
+ SvFileStream aFileStream(sUrl, StreamMode::READ);
+ vcl::PngImageReader aPNGReader(aFileStream);
+ aPNGReader.read(rParameters.mrBitmap);
+ return true;
+}
+
+void cacheBitmapToDisk(std::u16string_view sVariant, ImageRequestParameters const & rParameters)
+{
+ OUString sUrl(createIconCacheUrl(sVariant, rParameters));
+ try
+ {
+ SvFileStream aStream(sUrl, StreamMode::WRITE);
+ vcl::PngImageWriter aWriter(aStream);
+ aWriter.write(rParameters.mrBitmap);
+ aStream.Close();
+ }
+ catch (...)
+ {}
+}
+
+} // end anonymous namespace
+
+bool ImplImageTree::doLoadImage(ImageRequestParameters& rParameters)
+{
+ setStyle(rParameters.msStyle);
+
+ if (iconCacheLookup(rParameters))
+ return true;
+
+ OUString aVariant = createVariant(rParameters);
+ if (loadDiskCachedVersion(aVariant, rParameters))
+ return true;
+
+ if (!rParameters.mrBitmap.IsEmpty())
+ rParameters.mrBitmap.SetEmpty();
+
+ LanguageTag aLanguageTag = Application::GetSettings().GetUILanguageTag();
+
+ std::vector<OUString> aPaths = getPaths(rParameters.msName, aLanguageTag);
+
+ bool bFound = false;
+
+ try
+ {
+ bFound = findImage(aPaths, rParameters);
+ }
+ catch (uno::RuntimeException&)
+ {
+ throw;
+ }
+ catch (const uno::Exception&)
+ {
+ TOOLS_INFO_EXCEPTION("vcl", "ImplImageTree::doLoadImage");
+ }
+
+ if (bFound)
+ {
+ if (rParameters.mbWriteImageToCache)
+ {
+ cacheBitmapToDisk(aVariant, rParameters);
+ }
+ getIconCache(rParameters)[rParameters.msName] = std::make_pair(rParameters.mbLocalized, rParameters.mrBitmap);
+ }
+
+ return bFound;
+}
+
+void ImplImageTree::shutdown()
+{
+ maCurrentStyle.clear();
+ maIconSets.clear();
+}
+
+void ImplImageTree::setStyle(OUString const & style)
+{
+ assert(!style.isEmpty());
+ if (style != maCurrentStyle)
+ {
+ maCurrentStyle = style;
+ createStyle();
+ }
+}
+
+/**
+ * The vcldemo app doesn't set up all the config stuff that the main app does, so we need another
+ * way of finding the cursor images.
+ */
+static bool isVclDemo()
+{
+ static const bool bVclDemoOverride = std::getenv("LIBO_VCL_DEMO") != nullptr;
+ return bVclDemoOverride;
+}
+
+void ImplImageTree::createStyle()
+{
+ if (maIconSets.find(maCurrentStyle) != maIconSets.end())
+ return;
+
+ OUString sThemeUrl;
+
+ if (isVclDemo())
+ {
+ if (maCurrentStyle == "default")
+ sThemeUrl = "file://" SRC_ROOT "/icon-themes/colibre-svg";
+ else
+ sThemeUrl = "file://" SRC_ROOT "/icon-themes/" + maCurrentStyle;
+ }
+ else if (maCurrentStyle != "default")
+ {
+ OUString paths = vcl::IconThemeScanner::GetStandardIconThemePath();
+ std::deque<OUString> aPaths;
+ sal_Int32 nIndex = 0;
+ do
+ {
+ aPaths.push_front(paths.getToken(0, ';', nIndex));
+ }
+ while (nIndex >= 0);
+
+ for (const auto& path : aPaths)
+ {
+ INetURLObject aUrl(path);
+ OSL_ASSERT(!aUrl.HasError());
+
+ bool ok = aUrl.Append(Concat2View("images_" + maCurrentStyle), INetURLObject::EncodeMechanism::All);
+ OSL_ASSERT(ok);
+ sThemeUrl = aUrl.GetMainURL(INetURLObject::DecodeMechanism::NONE) + ".zip";
+ if (urlExists(sThemeUrl))
+ break;
+ sThemeUrl.clear();
+ }
+
+ if (sThemeUrl.isEmpty())
+ return;
+ }
+ else
+ {
+ sThemeUrl += "images";
+ if (!urlExists(sThemeUrl))
+ return;
+ }
+
+ maIconSets[maCurrentStyle] = IconSet(sThemeUrl);
+
+ loadImageLinks();
+}
+
+/// Find an icon cache for the right scale factor
+ImplImageTree::IconCache &ImplImageTree::getIconCache(const ImageRequestParameters& rParameters)
+{
+ IconSet &rSet = getCurrentIconSet();
+ auto it = rSet.maScaledIconCaches.find(rParameters.mnScalePercentage);
+ if ( it != rSet.maScaledIconCaches.end() )
+ return it->second;
+ rSet.maScaledIconCaches.emplace(rParameters.mnScalePercentage, IconCache());
+ return rSet.maScaledIconCaches[rParameters.mnScalePercentage];
+}
+
+bool ImplImageTree::iconCacheLookup(ImageRequestParameters& rParameters)
+{
+ IconCache& rIconCache = getIconCache(rParameters);
+
+ IconCache::iterator i(rIconCache.find(getRealImageName(rParameters.msName)));
+ if (i != rIconCache.end() && i->second.first == rParameters.mbLocalized)
+ {
+ rParameters.mrBitmap = i->second.second;
+ return true;
+ }
+
+ return false;
+}
+
+bool ImplImageTree::findImage(std::vector<OUString> const & rPaths, ImageRequestParameters& rParameters)
+{
+ if (!checkPathAccess())
+ return false;
+
+ uno::Reference<container::XNameAccess> const & rNameAccess = getCurrentIconSet().maNameAccess;
+
+ for (OUString const & rPath : rPaths)
+ {
+ if (rNameAccess->hasByName(rPath))
+ {
+ uno::Reference<io::XInputStream> aStream;
+ bool ok = rNameAccess->getByName(rPath) >>= aStream;
+ assert(ok);
+ (void)ok; // prevent unused warning in release build
+
+ loadImageFromStream(wrapStream(aStream), rPath, rParameters);
+
+ return true;
+ }
+ }
+ return false;
+}
+
+void ImplImageTree::loadImageLinks()
+{
+ static constexpr OUString aLinkFilename(u"links.txt"_ustr);
+
+ if (!checkPathAccess())
+ return;
+
+ const uno::Reference<container::XNameAccess> &rNameAccess = getCurrentIconSet().maNameAccess;
+
+ if (rNameAccess->hasByName(aLinkFilename))
+ {
+ uno::Reference<io::XInputStream> xStream;
+ bool ok = rNameAccess->getByName(aLinkFilename) >>= xStream;
+ assert(ok);
+ (void)ok; // prevent unused warning in release build
+
+ parseLinkFile(wrapStream(xStream));
+ return;
+ }
+}
+
+void ImplImageTree::parseLinkFile(std::shared_ptr<SvStream> const & xStream)
+{
+ OStringBuffer aLine;
+ OUString aLink, aOriginal;
+ int nLineNo = 0;
+ while (xStream->ReadLine(aLine))
+ {
+ ++nLineNo;
+ if (aLine.isEmpty())
+ continue;
+
+ sal_Int32 nIndex = 0;
+ aLink = OStringToOUString(o3tl::getToken(aLine, 0, ' ', nIndex), RTL_TEXTENCODING_UTF8);
+ aOriginal = OStringToOUString(o3tl::getToken(aLine, 0, ' ', nIndex), RTL_TEXTENCODING_UTF8);
+
+ // skip comments, or incomplete entries
+ if (aLink.isEmpty() || aLink[0] == '#' || aOriginal.isEmpty())
+ {
+ if (aLink.isEmpty() || aOriginal.isEmpty())
+ SAL_WARN("vcl", "ImplImageTree::parseLinkFile: icon links.txt parse error, incomplete link at line " << nLineNo);
+ continue;
+ }
+
+ getCurrentIconSet().maLinkHash[aLink] = aOriginal;
+
+ OUString aOriginal32 = convertLcTo32Path(aOriginal);
+ OUString aLink32 = convertLcTo32Path(aLink);
+
+ if (!aOriginal32.isEmpty() && !aLink32.isEmpty())
+ getCurrentIconSet().maLinkHash[aLink32] = aOriginal32;
+ }
+}
+
+OUString const & ImplImageTree::getRealImageName(OUString const & rIconName)
+{
+ IconLinkHash & rLinkHash = maIconSets[maCurrentStyle].maLinkHash;
+
+ OUString sNameWithNoExtension = getNameNoExtension(rIconName);
+
+ // PNG is priority
+ auto it = rLinkHash.find(sNameWithNoExtension + ".png");
+ if (it != rLinkHash.end())
+ return it->second;
+
+ // also check SVG name
+ it = rLinkHash.find(sNameWithNoExtension + ".svg");
+ if (it != rLinkHash.end())
+ return it->second;
+
+ // neither was found so just return the original name
+ return rIconName;
+}
+
+namespace {
+
+class FolderFileAccess : public ::cppu::WeakImplHelper<css::container::XNameAccess>
+{
+public:
+ uno::Reference< uno::XComponentContext > mxContext;
+ OUString maURL;
+ FolderFileAccess(uno::Reference< uno::XComponentContext > context, OUString url)
+ : mxContext(std::move(context)), maURL(std::move(url)) {}
+ // XElementAccess
+ virtual css::uno::Type SAL_CALL getElementType() override { return cppu::UnoType<io::XInputStream>::get(); }
+ virtual sal_Bool SAL_CALL hasElements() override { return true; }
+ // XNameAccess
+ virtual css::uno::Any SAL_CALL getByName( const OUString& aName ) override
+ {
+ uno::Reference< io::XInputStream > xInputStream = ucb::SimpleFileAccess::create(mxContext)->openFileRead( maURL + "/" + aName );
+ return css::uno::Any(xInputStream);
+ }
+ virtual css::uno::Sequence< OUString > SAL_CALL getElementNames() override
+ {
+ return {};
+ }
+ virtual sal_Bool SAL_CALL hasByName( const OUString& aName ) override
+ {
+ osl::File aBaseFile(maURL + "/" + aName);
+ return osl::File::E_None == aBaseFile.open(osl_File_OpenFlag_Read);
+ }
+};
+
+}
+
+bool ImplImageTree::checkPathAccess()
+{
+ IconSet& rIconSet = getCurrentIconSet();
+ uno::Reference<container::XNameAccess> & rNameAccess = rIconSet.maNameAccess;
+ if (rNameAccess.is())
+ return true;
+
+ try
+ {
+ if (isVclDemo())
+ rNameAccess = new FolderFileAccess(comphelper::getProcessComponentContext(), rIconSet.maURL);
+ else
+ rNameAccess = packages::zip::ZipFileAccess::createWithURL(comphelper::getProcessComponentContext(), rIconSet.maURL);
+ }
+ catch (const uno::RuntimeException &)
+ {
+ throw;
+ }
+ catch (const uno::Exception &)
+ {
+ TOOLS_INFO_EXCEPTION("vcl", "ImplImageTree::zip file location " << rIconSet.maURL);
+ return false;
+ }
+ return rNameAccess.is();
+}
+
+uno::Reference<container::XNameAccess> const & ImplImageTree::getNameAccess()
+{
+ (void)checkPathAccess();
+ return getCurrentIconSet().maNameAccess;
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */