// Copyright 2022 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // IMPORTANT NOTE: All QtShim members that use `delegate_` must be decorated // with DISABLE_CFI_VCALL. #include "ui/qt/qt_shim.h" #include #include #include #include #include #include #include #include #include #include namespace qt { namespace { bool IsStyleItalic(QFont::Style style) { switch (style) { case QFont::Style::StyleNormal: return false; case QFont::Style::StyleItalic: // gfx::Font::FontStyle doesn't support oblique, so treat it as italic. case QFont::Style::StyleOblique: return true; } } FontHinting QtHintingToFontHinting(QFont::HintingPreference hinting) { switch (hinting) { case QFont::PreferDefaultHinting: return FontHinting::kDefault; case QFont::PreferNoHinting: return FontHinting::kNone; case QFont::PreferVerticalHinting: // QT treats vertical hinting as "light" for Freetype: // https://doc.qt.io/qt-5/qfont.html#HintingPreference-enum return FontHinting::kLight; case QFont::PreferFullHinting: return FontHinting::kFull; } } // Obtain the average color of a gradient. SkColor GradientColor(const QGradient& gradient) { QGradientStops stops = gradient.stops(); if (stops.empty()) return qRgba(0, 0, 0, 0); float a = 0; float r = 0; float g = 0; float b = 0; for (int i = 0; i < stops.size(); i++) { // Determine the extents of this stop. The whole gradient interval is [0, // 1], so extend to the endpoint if this is the first or last stop. float left_interval = i == 0 ? stops[i].first : (stops[i].first - stops[i - 1].first) / 2; float right_interval = i == stops.size() - 1 ? 1 - stops[i].first : (stops[i + 1].first - stops[i].first) / 2; float length = left_interval + right_interval; // alpha() returns a value in [0, 255] and alphaF() returns a value in // [0, 1]. The color values are not premultiplied so the RGB channels need // to be multiplied by the alpha (in range [0, 1]) before summing. The // alpha doesn't need to be multiplied, so we just sum color.alpha() in // range [0, 255] directly. const QColor& color = stops[i].second; a += color.alpha() * length; r += color.alphaF() * color.red() * length; g += color.alphaF() * color.green() * length; b += color.alphaF() * color.blue() * length; } return qRgba(r, g, b, a); } // Obtain the average color of a texture. SkColor TextureColor(QImage image) { size_t size = image.width() * image.height(); if (!size) return qRgba(0, 0, 0, 0); if (image.format() != QImage::Format_ARGB32_Premultiplied) image = image.convertToFormat(QImage::Format_ARGB32_Premultiplied); size_t a = 0; size_t r = 0; size_t g = 0; size_t b = 0; const auto* pixels = reinterpret_cast(image.bits()); for (size_t i = 0; i < size; i++) { auto color = QColor::fromRgba(pixels[i]); a += color.alpha(); r += color.red(); g += color.green(); b += color.blue(); } return qRgba(r / size, g / size, b / size, a / size); } SkColor BrushColor(const QBrush& brush) { QColor color = brush.color(); auto alpha_blend = [&](uint8_t alpha) { QColor blended = color; blended.setAlpha(blended.alpha() * alpha / 255); return blended.rgba(); }; switch (brush.style()) { case Qt::SolidPattern: return alpha_blend(0xFF); case Qt::Dense1Pattern: return alpha_blend(0xE0); case Qt::Dense2Pattern: return alpha_blend(0xC0); case Qt::Dense3Pattern: return alpha_blend(0xA0); case Qt::Dense4Pattern: return alpha_blend(0x80); case Qt::Dense5Pattern: return alpha_blend(0x60); case Qt::Dense6Pattern: return alpha_blend(0x40); case Qt::Dense7Pattern: return alpha_blend(0x20); case Qt::NoBrush: return alpha_blend(0x00); case Qt::HorPattern: case Qt::VerPattern: return alpha_blend(0x20); case Qt::CrossPattern: return alpha_blend(0x40); case Qt::BDiagPattern: case Qt::FDiagPattern: return alpha_blend(0x20); case Qt::DiagCrossPattern: return alpha_blend(0x40); case Qt::LinearGradientPattern: case Qt::RadialGradientPattern: case Qt::ConicalGradientPattern: return GradientColor(*brush.gradient()); case Qt::TexturePattern: return TextureColor(brush.textureImage()); } } QPalette::ColorRole ColorTypeToColorRole(ColorType type) { switch (type) { case ColorType::kWindowBg: return QPalette::Window; case ColorType::kWindowFg: return QPalette::WindowText; case ColorType::kHighlightBg: return QPalette::Highlight; case ColorType::kHighlightFg: return QPalette::HighlightedText; case ColorType::kEntryBg: return QPalette::Base; case ColorType::kEntryFg: return QPalette::Text; case ColorType::kButtonBg: return QPalette::Button; case ColorType::kButtonFg: return QPalette::ButtonText; case ColorType::kLight: return QPalette::Light; case ColorType::kMidlight: return QPalette::Midlight; case ColorType::kMidground: return QPalette::Mid; case ColorType::kDark: return QPalette::Dark; case ColorType::kShadow: return QPalette::Shadow; } } QPalette::ColorGroup ColorStateToColorGroup(ColorState state) { switch (state) { case ColorState::kNormal: return QPalette::Normal; case ColorState::kDisabled: return QPalette::Disabled; case ColorState::kInactive: return QPalette::Inactive; } } } // namespace QtShim::QtShim(QtInterface::Delegate* delegate, int* argc, char** argv) : delegate_(delegate), app_(*argc, argv) { connect(&app_, SIGNAL(fontChanged(const QFont&)), this, SLOT(FontChanged(const QFont&))); connect(&app_, SIGNAL(paletteChanged(const QPalette&)), this, SLOT(PaletteChanged(const QPalette&))); } QtShim::~QtShim() = default; double QtShim::GetScaleFactor() const { return app_.devicePixelRatio(); } FontRenderParams QtShim::GetFontRenderParams() const { QFont font = app_.font(); auto style = font.styleStrategy(); return { .antialiasing = !(style & QFont::StyleStrategy::NoAntialias), .use_bitmaps = !!(style & QFont::StyleStrategy::PreferBitmap), .hinting = QtHintingToFontHinting(font.hintingPreference()), }; } FontDescription QtShim::GetFontDescription() const { QFont font = app_.font(); return { .family = String(font.family().toStdString().c_str()), .size_pixels = font.pixelSize(), .size_points = font.pointSize(), .is_italic = IsStyleItalic(font.style()), .weight = font.weight(), }; } Image QtShim::GetIconForContentType(const String& content_type, int size) const { QMimeDatabase db; for (const char* mime : {content_type.c_str(), "application/octet-stream"}) { auto mt = db.mimeTypeForName(mime); for (const auto& name : {mt.iconName(), mt.genericIconName()}) { auto icon = QIcon::fromTheme(name); auto pixmap = icon.pixmap(size); auto image = pixmap.toImage(); if (image.format() != QImage::Format_ARGB32_Premultiplied) image = image.convertToFormat(QImage::Format_ARGB32_Premultiplied); if (auto bytes = image.sizeInBytes()) { return {image.width(), image.height(), static_cast(image.devicePixelRatio()), Buffer(image.bits(), bytes)}; } } } return {}; } SkColor QtShim::GetColor(ColorType role, ColorState state) const { return BrushColor(app_.palette().brush(ColorStateToColorGroup(state), ColorTypeToColorRole(role))); } SkColor QtShim::GetFrameColor(ColorState state, bool use_custom_frame) const { constexpr int kSampleSize = 32; return TextureColor(DrawHeaderImpl(kSampleSize, kSampleSize, GetColor(ColorType::kWindowBg, state), state, use_custom_frame)); } int QtShim::GetCursorBlinkIntervalMs() const { return app_.cursorFlashTime(); } int QtShim::GetAnimationDurationMs() const { return app_.style()->styleHint(QStyle::SH_Widget_Animation_Duration); } DISABLE_CFI_VCALL void QtShim::FontChanged(const QFont& font) { delegate_->FontChanged(); } DISABLE_CFI_VCALL void QtShim::PaletteChanged(const QPalette& palette) { delegate_->ThemeChanged(); } Image QtShim::DrawHeader(int width, int height, SkColor default_color, ColorState state, bool use_custom_frame) const { QImage image = DrawHeaderImpl(width, height, default_color, state, use_custom_frame); return {width, height, 1.0f, Buffer(image.bits(), image.sizeInBytes())}; } QImage QtShim::DrawHeaderImpl(int width, int height, SkColor default_color, ColorState state, bool use_custom_frame) const { QImage image(width, height, QImage::Format_ARGB32_Premultiplied); image.fill(default_color); QPainter painter(&image); if (use_custom_frame) { // Chrome renders it's own window border, so clip the border out by // rendering the titlebar larger than the image. constexpr int kBorderWidth = 5; QStyleOptionTitleBar opt; opt.rect = QRect(-kBorderWidth, -kBorderWidth, width + 2 * kBorderWidth, height + 2 * kBorderWidth); if (state == ColorState::kNormal) opt.titleBarState = QStyle::State_Active; app_.style()->drawComplexControl(QStyle::CC_TitleBar, &opt, &painter, nullptr); } else { painter.fillRect( 0, 0, width, height, app_.palette().brush(ColorStateToColorGroup(state), QPalette::Window)); } return image; } } // namespace qt qt::QtInterface* CreateQtInterface(qt::QtInterface::Delegate* delegate, int* argc, char** argv) { return new qt::QtShim(delegate, argc, argv); }