/*************************************************************************** * Copyright (c) 2004 Jürgen Riegel * * * * This file is part of the FreeCAD CAx development system. * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Library General Public * * License as published by the Free Software Foundation; either * * version 2 of the License, or (at your option) any later version. * * * * This library is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU Library General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this library; see the file COPYING.LIB. If not, * * write to the Free Software Foundation, Inc., 59 Temple Place, * * Suite 330, Boston, MA 02111-1307, USA * * * ***************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "BitmapFactory.h" using namespace Gui; namespace Gui { class BitmapFactoryInstP { public: QMap xpmCache; bool useIconTheme; }; } // namespace Gui BitmapFactoryInst* BitmapFactoryInst::_pcSingleton = nullptr; BitmapFactoryInst& BitmapFactoryInst::instance() { if (!_pcSingleton) { _pcSingleton = new BitmapFactoryInst; std::map::const_iterator it; it = App::GetApplication().Config().find("ProgramIcons"); if (it != App::GetApplication().Config().end()) { QString home = QString::fromStdString(App::Application::getHomePath()); QString path = QString::fromUtf8(it->second.c_str()); if (QDir(path).isRelative()) { path = QFileInfo(QDir(home), path).absoluteFilePath(); } _pcSingleton->addPath(path); } _pcSingleton->addPath( QStringLiteral("%1/icons").arg(QString::fromStdString(App::Application::getHomePath())) ); _pcSingleton->addPath( QStringLiteral("%1/icons").arg(QString::fromStdString(App::Application::getUserAppDataDir())) ); _pcSingleton->addPath(QLatin1String(":/icons/")); _pcSingleton->addPath(QLatin1String(":/Icons/")); } return *_pcSingleton; } void BitmapFactoryInst::destruct() { if (_pcSingleton) { delete _pcSingleton; } _pcSingleton = nullptr; } BitmapFactoryInst::BitmapFactoryInst() { d = new BitmapFactoryInstP; restoreCustomPaths(); configureUseIconTheme(); } BitmapFactoryInst::~BitmapFactoryInst() { delete d; } void BitmapFactoryInst::restoreCustomPaths() { Base::Reference group = App::GetApplication().GetParameterGroupByPath( "User parameter:BaseApp/Preferences/Bitmaps" ); std::vector paths = group->GetASCIIs("CustomPath"); for (auto& path : paths) { addPath(QString::fromUtf8(path.c_str())); } } void Gui::BitmapFactoryInst::configureUseIconTheme() { Base::Reference group = App::GetApplication().GetParameterGroupByPath( "User parameter:BaseApp/Preferences/Bitmaps/Theme" ); d->useIconTheme = group->GetBool("UseIconTheme", group->GetBool("ThemeSearchPaths", false)); } void BitmapFactoryInst::addPath(const QString& path) { QDir::addSearchPath(QStringLiteral("icons"), path); } void BitmapFactoryInst::removePath(const QString& path) { QStringList iconPaths = QDir::searchPaths(QStringLiteral("icons")); int pos = iconPaths.indexOf(path); if (pos != -1) { iconPaths.removeAt(pos); QDir::setSearchPaths(QStringLiteral("icons"), iconPaths); } } QStringList BitmapFactoryInst::getPaths() const { return QDir::searchPaths(QStringLiteral("icons")); } QStringList BitmapFactoryInst::findIconFiles() const { QStringList files, filters; QList formats = QImageReader::supportedImageFormats(); for (QList::iterator it = formats.begin(); it != formats.end(); ++it) { filters << QStringLiteral("*.%1").arg(QString::fromLatin1(*it).toLower()); } QStringList paths = QDir::searchPaths(QStringLiteral("icons")); paths.removeDuplicates(); for (QStringList::Iterator pt = paths.begin(); pt != paths.end(); ++pt) { QDir d(*pt); d.setNameFilters(filters); QFileInfoList fi = d.entryInfoList(); for (QFileInfoList::iterator it = fi.begin(); it != fi.end(); ++it) { files << it->absoluteFilePath(); } } files.removeDuplicates(); return files; } void BitmapFactoryInst::addPixmapToCache(const char* name, const QPixmap& icon) { d->xpmCache[name] = icon; } bool BitmapFactoryInst::findPixmapInCache(const char* name, QPixmap& px) const { QMap::Iterator it = d->xpmCache.find(name); if (it != d->xpmCache.end()) { px = it.value(); return true; } return false; } QIcon BitmapFactoryInst::iconFromTheme(const char* name, const QIcon& fallback) { if (!d->useIconTheme) { return iconFromDefaultTheme(name, fallback); } QString iconName = QString::fromUtf8(name); QIcon icon = QIcon::fromTheme(iconName, fallback); if (icon.isNull()) { QPixmap px = pixmap(name); if (!px.isNull()) { icon.addPixmap(px); } } return icon; } bool BitmapFactoryInst::loadPixmap(const QString& filename, QPixmap& icon) const { QFileInfo fi(filename); if (fi.exists()) { // first check if it's an SVG because Qt's qsvg4 module shouldn't be used therefore if (fi.suffix().toLower() == QLatin1String("svg")) { QFile svgFile(fi.filePath()); if (svgFile.open(QFile::ReadOnly | QFile::Text)) { QByteArray content = svgFile.readAll(); icon = pixmapFromSvg(content, QSize(64, 64)); } } else { // try with Qt plugins icon.load(fi.filePath()); } } return !icon.isNull(); } QIcon Gui::BitmapFactoryInst::iconFromDefaultTheme(const char* name, const QIcon& fallback) { QIcon icon; QPixmap px = pixmap(name); if (!px.isNull()) { icon.addPixmap(px); return icon; } else { return fallback; } return icon; } QPixmap BitmapFactoryInst::pixmap(const char* name) const { if (!name || *name == '\0') { return {}; } // as very first test check whether the pixmap is in the cache QMap::Iterator it = d->xpmCache.find(name); if (it != d->xpmCache.end()) { return it.value(); } QPixmap icon; // Try whether an absolute path is given QString fn = QString::fromUtf8(name); loadPixmap(fn, icon); // try to find it in the 'icons' search paths if (icon.isNull()) { QList formats = QImageReader::supportedImageFormats(); formats.prepend("SVG"); // check first for SVG to use special import mechanism QString fileName = QStringLiteral("icons:") + fn; if (!loadPixmap(fileName, icon)) { // Go through supported file formats for (QList::iterator fm = formats.begin(); fm != formats.end(); ++fm) { QString path = QStringLiteral("%1.%2").arg( fileName, QString::fromLatin1((*fm).toLower().constData()) ); if (loadPixmap(path, icon)) { break; } } } } if (!icon.isNull()) { d->xpmCache[name] = icon; return icon; } Base::Console().warning("Cannot find icon: %s\n", name); return QPixmap(Gui::BitmapFactory().pixmapFromSvg("help-browser", QSize(16, 16))); } QPixmap BitmapFactoryInst::pixmapFromSvg( const char* name, const QSizeF& size, const ColorMap& colorMapping ) const { static qreal dpr = getMaximumDPR(); // If an absolute path is given QPixmap icon; QString iconPath; QString fn = QString::fromUtf8(name); if (QFile(fn).exists()) { iconPath = fn; } // try to find it in the 'icons' search paths if (iconPath.isEmpty()) { QString fileName = QStringLiteral("icons:") + fn; QFileInfo fi(fileName); if (fi.exists()) { iconPath = fi.filePath(); } else { fileName += QLatin1String(".svg"); fi.setFile(fileName); if (fi.exists()) { iconPath = fi.filePath(); } } } if (!iconPath.isEmpty()) { QFile file(iconPath); if (file.open(QFile::ReadOnly | QFile::Text)) { QByteArray content = file.readAll(); icon = pixmapFromSvg(content, size * dpr, colorMapping); } } if (!icon.isNull()) { icon.setDevicePixelRatio(dpr); } return icon; } QPixmap BitmapFactoryInst::pixmapFromSvg( const QByteArray& originalContents, const QSizeF& size, const ColorMap& colorMapping ) const { QString stringContents = QString::fromUtf8(originalContents); for (const auto& colorToColor : colorMapping) { ulong fromColor = colorToColor.first; ulong toColor = colorToColor.second; QString fromColorString = QStringLiteral("#%1").arg(fromColor, 6, 16, QChar::fromLatin1('0')); QString toColorString = QStringLiteral("#%1").arg(toColor, 6, 16, QChar::fromLatin1('0')); stringContents = stringContents.replace(fromColorString, toColorString); } QByteArray contents = stringContents.toUtf8(); QImage image(size.toSize(), QImage::Format_ARGB32_Premultiplied); image.fill(0x00000000); QPainter p(&image); QSvgRenderer svg; { // tmp. disable the report window to suppress some bothering warnings const Base::ILoggerBlocker blocker("ReportOutput", Base::ConsoleSingleton::MsgType_Wrn); svg.load(contents); } svg.render(&p); p.end(); return QPixmap::fromImage(image); } QStringList BitmapFactoryInst::pixmapNames() const { QStringList names; for (QMap::Iterator It = d->xpmCache.begin(); It != d->xpmCache.end(); ++It) { QString item = QString::fromUtf8(It.key().c_str()); if (!names.contains(item)) { names << item; } } return names; } QPixmap BitmapFactoryInst::resize(int w, int h, const QPixmap& p, Qt::BGMode bgmode) const { if (bgmode == Qt::TransparentMode) { if (p.width() == 0 || p.height() == 0) { w = 1; } QPixmap pix = p; int x = pix.width() > w ? 0 : (w - pix.width()) / 2; int y = pix.height() > h ? 0 : (h - pix.height()) / 2; if (x == 0 && y == 0) { return pix; } QPixmap pm(w, h); QBitmap mask(w, h); mask.fill(Qt::color0); QBitmap bm = pix.mask(); if (!bm.isNull()) { QPainter painter(&mask); painter.drawPixmap(QPoint(x, y), bm, QRect(0, 0, pix.width(), pix.height())); pm.setMask(mask); } else { pm.setMask(mask); pm = fillRect(x, y, pix.width(), pix.height(), pm, Qt::OpaqueMode); } QPainter pt; pt.begin(&pm); pt.drawPixmap(x, y, pix); pt.end(); return pm; } else { // Qt::OpaqueMode QPixmap pix = p; if (pix.width() == 0 || pix.height() == 0) { return pix; // do not resize a null pixmap } QPalette pal = qApp->palette(); QColor dl = pal.color(QPalette::Disabled, QPalette::Light); QColor dt = pal.color(QPalette::Disabled, QPalette::Text); QPixmap pm(w, h); pm.fill(dl); QPainter pt; pt.begin(&pm); pt.setPen(dl); pt.drawPixmap(1, 1, pix); pt.setPen(dt); pt.drawPixmap(0, 0, pix); pt.end(); return pm; } } QPixmap BitmapFactoryInst::fillRect(int x, int y, int w, int h, const QPixmap& p, Qt::BGMode bgmode) const { QBitmap b = p.mask(); if (b.isNull()) { return p; // sorry, but cannot do anything } QPixmap pix = p; // modify the mask QPainter pt; pt.begin(&b); if (bgmode == Qt::OpaqueMode) { pt.fillRect(x, y, w, h, Qt::color1); // make opaque } else { // Qt::TransparentMode pt.fillRect(x, y, w, h, Qt::color0); // make transparent } pt.end(); pix.setMask(b); return pix; } QPixmap BitmapFactoryInst::merge(const QPixmap& p1, const QPixmap& p2, bool vertical) const { int width = 0; int height = 0; int x = 0; int y = 0; // get the size for the new pixmap if (vertical) { y = p1.height(); width = qMax(p1.width(), p2.width()); height = p1.height() + p2.height(); } else { x = p1.width(); width = p1.width() + p2.width(); height = qMax(p1.height(), p2.height()); } QPixmap res(width, height); QBitmap mask(width, height); QBitmap mask1 = p1.mask(); QBitmap mask2 = p2.mask(); mask.fill(Qt::color0); auto* pt1 = new QPainter(&res); pt1->drawPixmap(0, 0, p1); pt1->drawPixmap(x, y, p2); delete pt1; auto* pt2 = new QPainter(&mask); pt2->drawPixmap(0, 0, mask1); pt2->drawPixmap(x, y, mask2); delete pt2; res.setMask(mask); return res; } QPixmap BitmapFactoryInst::merge(const QPixmap& p1, const QPixmap& p2, Position pos) const { // does the similar as the method above except that this method does not resize the resulting pixmap int x = 0, y = 0; qreal dpr1 = p1.devicePixelRatio(); qreal dpr2 = p2.devicePixelRatio(); switch (pos) { case TopLeft: break; case TopRight: x = p1.width() / dpr1 - p2.width() / dpr2; break; case BottomLeft: y = p1.height() / dpr1 - p2.height() / dpr2; break; case BottomRight: x = p1.width() / dpr1 - p2.width() / dpr2; y = p1.height() / dpr1 - p2.height() / dpr2; break; } QPixmap p = p1; p = fillRect(x, y, p2.width(), p2.height(), p, Qt::OpaqueMode); QPainter pt; pt.begin(&p); pt.setPen(Qt::NoPen); pt.drawRect(x, y, p2.width(), p2.height()); pt.drawPixmap(x, y, p2); pt.end(); return p; } QPixmap BitmapFactoryInst::disabled(const QPixmap& p) const { QStyleOption opt; opt.palette = QApplication::palette(); return QApplication::style()->generatedIconPixmap(QIcon::Disabled, p, &opt); } QPixmap BitmapFactoryInst::empty(QSize size) const { qreal dpr = getMaximumDPR(); QPixmap res(size * dpr); res.fill(Qt::transparent); res.setDevicePixelRatio(dpr); return res; } void BitmapFactoryInst::convert(const QImage& p, SoSFImage& img) const { SbVec2s size; size[0] = p.width(); size[1] = p.height(); int buffersize = static_cast(p.sizeInBytes()); int numcomponents = 0; QVector table = p.colorTable(); if (!table.isEmpty()) { if (p.hasAlphaChannel()) { if (p.allGray()) { numcomponents = 2; } else { numcomponents = 4; } } else { if (p.allGray()) { numcomponents = 1; } else { numcomponents = 3; } } } else { numcomponents = buffersize / (size[0] * size[1]); } int depth = numcomponents; // Coin3D only supports up to 32-bit images if (numcomponents == 8) { numcomponents = 4; } // allocate image data img.setValue(size, numcomponents, nullptr); unsigned char* bytes = img.startEditing(size, numcomponents); int width = (int)size[0]; int height = (int)size[1]; for (int y = 0; y < height; y++) { unsigned char* line = &bytes[width * numcomponents * (height - (y + 1))]; for (int x = 0; x < width; x++) { QColor col = p.pixelColor(x, y); switch (depth) { default: break; case 1: { QRgb rgb = col.rgb(); line[0] = qGray(rgb); } break; case 2: { QRgb rgb = col.rgba(); line[0] = qGray(rgb); line[1] = qAlpha(rgb); } break; case 3: { QRgb rgb = col.rgb(); line[0] = qRed(rgb); line[1] = qGreen(rgb); line[2] = qBlue(rgb); } break; case 4: { QRgb rgb = col.rgba(); line[0] = qRed(rgb); line[1] = qGreen(rgb); line[2] = qBlue(rgb); line[3] = qAlpha(rgb); } break; case 8: { QRgba64 rgb = col.rgba64(); line[0] = qRed(rgb); line[1] = qGreen(rgb); line[2] = qBlue(rgb); line[3] = qAlpha(rgb); } break; } line += numcomponents; } } img.finishEditing(); } void BitmapFactoryInst::convert(const SoSFImage& p, QImage& img) const { SbVec2s size; int numcomponents; const unsigned char* bytes = p.getValue(size, numcomponents); if (!bytes) { return; } int width = (int)size[0]; int height = (int)size[1]; img = QImage(width, height, QImage::Format_RGB32); QRgb* bits = (QRgb*)img.bits(); for (int y = 0; y < height; y++) { const unsigned char* line = &bytes[width * numcomponents * (height - (y + 1))]; for (int x = 0; x < width; x++) { switch (numcomponents) { default: case 1: *bits++ = qRgb(line[0], line[0], line[0]); break; case 2: *bits++ = qRgba(line[0], line[0], line[0], line[1]); break; case 3: *bits++ = qRgb(line[0], line[1], line[2]); break; case 4: *bits++ = qRgba(line[0], line[1], line[2], line[3]); break; } line += numcomponents; } } } QIcon BitmapFactoryInst::mergePixmap( const QIcon& base, const QPixmap& px, Gui::BitmapFactoryInst::Position position ) { QIcon overlayedIcon; int w = QApplication::style()->pixelMetric(QStyle::PM_ListViewIconSize); overlayedIcon.addPixmap( Gui::BitmapFactory().merge(base.pixmap(w, w, QIcon::Normal, QIcon::Off), px, position), QIcon::Normal, QIcon::Off ); overlayedIcon.addPixmap( Gui::BitmapFactory().merge(base.pixmap(w, w, QIcon::Normal, QIcon::On), px, position), QIcon::Normal, QIcon::Off ); return overlayedIcon; } qreal BitmapFactoryInst::getMaximumDPR() { qreal dpr = 1.0F; for (QScreen* screen : QGuiApplication::screens()) { dpr = std::max(screen->devicePixelRatio(), dpr); } return dpr; }