diff options
Diffstat (limited to 'vcl/source/image')
-rw-r--r-- | vcl/source/image/Image.cxx | 165 | ||||
-rw-r--r-- | vcl/source/image/ImageRepository.cxx | 37 | ||||
-rw-r--r-- | vcl/source/image/ImageTree.cxx | 67 | ||||
-rw-r--r-- | vcl/source/image/ImplImage.cxx | 186 | ||||
-rw-r--r-- | vcl/source/image/ImplImageTree.cxx | 716 |
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: */ |