// This file is part of Desktop App Toolkit, // a set of libraries for developing nice desktop applications. // // For license and copyright information please follow this link: // https://github.com/desktop-app/legal/blob/master/LEGAL // #include "ui/style/style_core_icon.h" #include "ui/style/style_core_palette.h" #include "ui/style/style_core.h" #include "base/basic_types.h" #include #include namespace style { namespace internal { namespace { uint32 colorKey(QColor c) { return (((((uint32(c.red()) << 8) | uint32(c.green())) << 8) | uint32(c.blue())) << 8) | uint32(c.alpha()); } base::flat_map IconMasks; QMutex IconMasksMutex; base::flat_map, QPixmap> iconPixmaps; base::flat_set iconData; [[nodiscard]] QImage CreateIconMask( not_null mask, int scale, bool ignoreDpr = false) { const auto ratio = ignoreDpr ? 1 : DevicePixelRatio(); auto maskImage = QImage::fromData(mask->data(), mask->size(), "PNG"); maskImage.setDevicePixelRatio(ratio); Assert(!maskImage.isNull()); // images are laid out like this: // 100x 200x // 300x const auto realscale = scale * ratio; const auto width = maskImage.width() / 3; const auto height = maskImage.height() / 5; const auto one = QRect(0, 0, width, height); const auto two = QRect(width, 0, width * 2, height * 2); const auto three = QRect(0, height * 2, width * 3, height * 3); if (realscale == 100) { return maskImage.copy(one); } else if (realscale == 200) { return maskImage.copy(two); } else if (realscale == 300) { return maskImage.copy(three); } return maskImage.copy( (realscale > 200) ? three : two ).scaled( ConvertScale(width, scale) * ratio, ConvertScale(height, scale) * ratio, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); } [[nodiscard]] QImage ResolveIconMask(not_null mask) { QMutexLocker lock(&IconMasksMutex); if (const auto i = IconMasks.find(mask); i != end(IconMasks)) { return i->second; } return IconMasks.emplace( mask, CreateIconMask(mask, Scale()) ).first->second; } [[nodiscard]] QSize readGeneratedSize( const IconMask *mask, int scale, bool ignoreDpr = false) { auto data = mask->data(); auto size = mask->size(); auto generateTag = qstr("GENERATE:"); if (size > generateTag.size() && !memcmp(data, generateTag.data(), generateTag.size())) { size -= generateTag.size(); data += generateTag.size(); auto sizeTag = qstr("SIZE:"); if (size > sizeTag.size() && !memcmp(data, sizeTag.data(), sizeTag.size())) { size -= sizeTag.size(); data += sizeTag.size(); auto baForStream = QByteArray::fromRawData(reinterpret_cast(data), size); QDataStream stream(baForStream); stream.setVersion(QDataStream::Qt_5_1); qint32 width = 0, height = 0; stream >> width >> height; Assert(stream.status() == QDataStream::Ok); return QSize( ConvertScale(width, scale), ConvertScale(height, scale)); } else { Unexpected("Bad data in generated icon!"); } } return QSize(); } } // namespace MonoIcon::MonoIcon(const MonoIcon &other, const style::palette &palette) : _mask(other._mask) , _color( palette.colorAtIndex( style::main_palette::indexOfColor(other._color))) , _offset(other._offset) { } MonoIcon::MonoIcon(const IconMask *mask, Color color, QPoint offset) : _mask(mask) , _color(std::move(color)) , _offset(offset) { } void MonoIcon::reset() const { _pixmap = QPixmap(); _size = QSize(); } int MonoIcon::width() const { ensureLoaded(); return _size.width(); } int MonoIcon::height() const { ensureLoaded(); return _size.height(); } QSize MonoIcon::size() const { ensureLoaded(); return _size; } QPoint MonoIcon::offset() const { return _offset; } void MonoIcon::paint(QPainter &p, const QPoint &pos, int outerw) const { int w = width(), h = height(); QPoint fullOffset = pos + offset(); int partPosX = RightToLeft() ? (outerw - fullOffset.x() - w) : fullOffset.x(); int partPosY = fullOffset.y(); ensureLoaded(); if (_pixmap.isNull()) { p.fillRect(partPosX, partPosY, w, h, _color); } else { p.drawPixmap(partPosX, partPosY, _pixmap); } } void MonoIcon::fill(QPainter &p, const QRect &rect) const { ensureLoaded(); if (_pixmap.isNull()) { p.fillRect(rect, _color); } else { p.drawPixmap(rect, _pixmap, QRect(0, 0, _pixmap.width(), _pixmap.height())); } } void MonoIcon::paint(QPainter &p, const QPoint &pos, int outerw, QColor colorOverride) const { int w = width(), h = height(); QPoint fullOffset = pos + offset(); int partPosX = RightToLeft() ? (outerw - fullOffset.x() - w) : fullOffset.x(); int partPosY = fullOffset.y(); ensureLoaded(); if (_pixmap.isNull()) { p.fillRect(partPosX, partPosY, w, h, colorOverride); } else { ensureColorizedImage(colorOverride); p.drawImage(partPosX, partPosY, _colorizedImage); } } void MonoIcon::fill(QPainter &p, const QRect &rect, QColor colorOverride) const { ensureLoaded(); if (_pixmap.isNull()) { p.fillRect(rect, colorOverride); } else { ensureColorizedImage(colorOverride); p.drawImage(rect, _colorizedImage, _colorizedImage.rect()); } } void MonoIcon::paint( QPainter &p, const QPoint &pos, int outerw, const style::palette &paletteOverride) const { auto size = readGeneratedSize(_mask, Scale()); auto maskImage = QImage(); if (size.isEmpty()) { maskImage = CreateIconMask(_mask, Scale()); size = maskImage.size() / DevicePixelRatio(); } const auto w = size.width(); const auto h = size.height(); const auto fullOffset = pos + offset(); const auto partPosX = RightToLeft() ? (outerw - fullOffset.x() - w) : fullOffset.x(); const auto partPosY = fullOffset.y(); if (!maskImage.isNull()) { auto colorizedImage = QImage( maskImage.size(), QImage::Format_ARGB32_Premultiplied); colorizeImage(maskImage, _color[paletteOverride]->c, &colorizedImage); p.drawImage(partPosX, partPosY, colorizedImage); } else { p.fillRect(partPosX, partPosY, w, h, _color[paletteOverride]); } } void MonoIcon::fill( QPainter &p, const QRect &rect, const style::palette &paletteOverride) const { auto size = readGeneratedSize(_mask, Scale()); auto maskImage = QImage(); if (size.isEmpty()) { maskImage = CreateIconMask(_mask, Scale()); size = maskImage.size() / DevicePixelRatio(); } if (!maskImage.isNull()) { auto colorizedImage = QImage( maskImage.size(), QImage::Format_ARGB32_Premultiplied); colorizeImage(maskImage, _color[paletteOverride]->c, &colorizedImage); p.drawImage(rect, colorizedImage, colorizedImage.rect()); } else { p.fillRect(rect, _color[paletteOverride]); } } QImage MonoIcon::instance( QColor colorOverride, int scale, bool ignoreDpr) const { if (scale == kScaleAuto) { ensureLoaded(); auto result = QImage( size() * DevicePixelRatio(), QImage::Format_ARGB32_Premultiplied); result.setDevicePixelRatio(DevicePixelRatio()); if (_pixmap.isNull()) { result.fill(colorOverride); } else { colorizeImage(_maskImage, colorOverride, &result); } return result; } const auto ratio = ignoreDpr ? 1 : DevicePixelRatio(); auto size = readGeneratedSize(_mask, scale, ignoreDpr); if (!size.isEmpty()) { auto result = QImage( size * ratio, QImage::Format_ARGB32_Premultiplied); result.setDevicePixelRatio(ratio); result.fill(colorOverride); return result; } auto mask = CreateIconMask(_mask, scale, ignoreDpr); auto result = QImage(mask.size(), QImage::Format_ARGB32_Premultiplied); result.setDevicePixelRatio(ratio); colorizeImage(mask, colorOverride, &result); return result; } void MonoIcon::ensureLoaded() const { if (_size.isValid()) { return; } else if (!_maskImage.isNull()) { createCachedPixmap(); return; } _size = readGeneratedSize(_mask, Scale()); if (_size.isEmpty()) { _maskImage = ResolveIconMask(_mask); createCachedPixmap(); } } void MonoIcon::ensureColorizedImage(QColor color) const { if (_colorizedImage.isNull()) { _colorizedImage = QImage( _maskImage.size(), QImage::Format_ARGB32_Premultiplied); } colorizeImage(_maskImage, color, &_colorizedImage); } void MonoIcon::createCachedPixmap() const { auto key = qMakePair(_mask, colorKey(_color->c)); auto j = iconPixmaps.find(key); if (j == end(iconPixmaps)) { auto image = colorizeImage(_maskImage, _color); j = iconPixmaps.emplace( key, QPixmap::fromImage(std::move(image))).first; } _pixmap = j->second; _size = _pixmap.size() / DevicePixelRatio(); } IconData::IconData(const IconData &other, const style::palette &palette) { created(); _parts.reserve(other._parts.size()); for (const auto &part : other._parts) { _parts.push_back(MonoIcon(part, palette)); } } void IconData::created() { iconData.emplace(this); } IconData::~IconData() { iconData.remove(this); } void IconData::fill(QPainter &p, const QRect &rect) const { if (_parts.empty()) return; auto partSize = _parts[0].size(); for (const auto &part : _parts) { Assert(part.offset() == QPoint(0, 0)); Assert(part.size() == partSize); part.fill(p, rect); } } void IconData::fill(QPainter &p, const QRect &rect, QColor colorOverride) const { if (_parts.empty()) return; auto partSize = _parts[0].size(); for (const auto &part : _parts) { Assert(part.offset() == QPoint(0, 0)); Assert(part.size() == partSize); part.fill(p, rect, colorOverride); } } QImage IconData::instance( QColor colorOverride, int scale, bool ignoreDpr) const { Expects(_parts.size() == 1); auto &part = _parts[0]; Assert(part.offset() == QPoint(0, 0)); return part.instance(colorOverride, scale, ignoreDpr); } int IconData::width() const { if (_width < 0) { _width = 0; for (const auto &part : _parts) { accumulate_max(_width, part.offset().x() + part.width()); } } return _width; } int IconData::height() const { if (_height < 0) { _height = 0; for (const auto &part : _parts) { accumulate_max(_height, part.offset().x() + part.height()); } } return _height; } Icon Icon::withPalette(const style::palette &palette) const { Expects(_data != nullptr); auto result = Icon(Qt::Uninitialized); result._data = new IconData(*_data, palette); result._owner = true; return result; } void ResetIcons() { iconPixmaps.clear(); for (const auto data : iconData) { data->reset(); } } void DestroyIcons() { iconData.clear(); iconPixmaps.clear(); QMutexLocker lock(&IconMasksMutex); IconMasks.clear(); } } // namespace internal } // namespace style