#include "widget/wpixmapstore.h" #include #include #include #include "skin/imgloader.h" #include "util/math.h" #include "util/memory.h" #include "util/painterscope.h" // static Paintable::DrawMode Paintable::DrawModeFromString(const QString& str) { if (str.compare("FIXED", Qt::CaseInsensitive) == 0) { return FIXED; } else if (str.compare("STRETCH", Qt::CaseInsensitive) == 0) { return STRETCH; } else if (str.compare("STRETCH_ASPECT", Qt::CaseInsensitive) == 0) { return STRETCH_ASPECT; } else if (str.compare("TILE", Qt::CaseInsensitive) == 0) { return TILE; } // Fall back on the implicit default from before Mixxx supported draw modes. qWarning() << "Unknown DrawMode string in DrawModeFromString:" << str << "using FIXED"; return FIXED; } // static QString Paintable::DrawModeToString(DrawMode mode) { switch (mode) { case FIXED: return "FIXED"; case STRETCH: return "STRETCH"; case STRETCH_ASPECT: return "STRETCH_ASPECT"; case TILE: return "TILE"; } // Fall back on the implicit default from before Mixxx supported draw modes. qWarning() << "Unknown DrawMode in DrawModeToString " << mode << "using FIXED"; return "FIXED"; } Paintable::Paintable(const PixmapSource& source, DrawMode mode, double scaleFactor) : m_drawMode(mode), m_source(source) { if (!source.isSVG()) { m_pPixmap.reset(WPixmapStore::getPixmapNoCache(source.getPath(), scaleFactor)); } else { auto pSvg = std::make_unique(); if (!source.getSvgSourceData().isEmpty()) { // Call here the different overload for svg content if (!pSvg->load(source.getSvgSourceData())) { // The above line already logs a warning return; } } else if (!source.getPath().isEmpty()) { if (!pSvg->load(source.getPath())) { // The above line already logs a warning return; } } else { return; } m_pSvg.reset(pSvg.release()); #ifdef __APPLE__ // Apple does Retina scaling behind the scenes, so we also pass a // Paintable::FIXED image. On the other targets, it is better to // cache the pixmap. We do not do this for TILE and color schemas. // which can result in a correct but possibly blurry picture at a // Retina display. This can be fixed when switching to QT5 if (mode == TILE || WPixmapStore::willCorrectColors()) { #else if (mode == TILE || mode == Paintable::FIXED || WPixmapStore::willCorrectColors()) { #endif // The SVG renderer doesn't directly support tiling, so we render // it to a pixmap which will then get tiled. QImage copy_buffer(m_pSvg->defaultSize() * scaleFactor, QImage::Format_ARGB32); copy_buffer.fill(0x00000000); // Transparent black. QPainter painter(©_buffer); m_pSvg->render(&painter); WPixmapStore::correctImageColors(©_buffer); m_pPixmap.reset(new QPixmap(copy_buffer.size())); m_pPixmap->convertFromImage(copy_buffer); } } } bool Paintable::isNull() const { return m_source.isEmpty(); } QSize Paintable::size() const { if (!m_pPixmap.isNull()) { return m_pPixmap->size(); } else if (!m_pSvg.isNull()) { return m_pSvg->defaultSize(); } return QSize(); } int Paintable::width() const { if (!m_pPixmap.isNull()) { return m_pPixmap->width(); } else if (!m_pSvg.isNull()) { QSize size = m_pSvg->defaultSize(); return size.width(); } return 0; } int Paintable::height() const { if (!m_pPixmap.isNull()) { return m_pPixmap->height(); } else if (!m_pSvg.isNull()) { QSize size = m_pSvg->defaultSize(); return size.height(); } return 0; } QRectF Paintable::rect() const { if (!m_pPixmap.isNull()) { return m_pPixmap->rect(); } else if (!m_pSvg.isNull()) { return QRectF(QPointF(0, 0), m_pSvg->defaultSize()); } return QRectF(); } void Paintable::draw(const QRectF& targetRect, QPainter* pPainter) { // The sourceRect is implicitly the entire Paintable. draw(targetRect, pPainter, rect()); } void Paintable::draw(int x, int y, QPainter* pPainter) { QRectF sourceRect(rect()); QRectF targetRect(QPointF(x, y), sourceRect.size()); draw(targetRect, pPainter, sourceRect); } void Paintable::draw(const QRectF& targetRect, QPainter* pPainter, const QRectF& sourceRect) { if (!targetRect.isValid() || !sourceRect.isValid() || isNull()) { return; } if (m_drawMode == FIXED) { // Only render the minimum overlapping rectangle between the source // and target. QSizeF fixedSize(math_min(sourceRect.width(), targetRect.width()), math_min(sourceRect.height(), targetRect.height())); QRectF adjustedTarget(targetRect.topLeft(), fixedSize); QRectF adjustedSource(sourceRect.topLeft(), fixedSize); return drawInternal(adjustedTarget, pPainter, adjustedSource); } else if (m_drawMode == STRETCH_ASPECT) { qreal sx = targetRect.width() / sourceRect.width(); qreal sy = targetRect.height() / sourceRect.height(); // Adjust the scale so that the scaling in both axes is equal. if (sx != sy) { qreal scale = math_min(sx, sy); QRectF adjustedTarget(targetRect.x(), targetRect.y(), scale * sourceRect.width(), scale * sourceRect.height()); return drawInternal(adjustedTarget, pPainter, sourceRect); } else { return drawInternal(targetRect, pPainter, sourceRect); } } else if (m_drawMode == STRETCH) { return drawInternal(targetRect, pPainter, sourceRect); } else if (m_drawMode == TILE) { return drawInternal(targetRect, pPainter, sourceRect); } } void Paintable::drawCentered(const QRectF& targetRect, QPainter* pPainter, const QRectF& sourceRect) { if (m_drawMode == FIXED) { // Only render the minimum overlapping rectangle between the source // and target. QSizeF fixedSize(math_min(sourceRect.width(), targetRect.width()), math_min(sourceRect.height(), targetRect.height())); QRectF adjustedSource(sourceRect.topLeft(), fixedSize); QRectF adjustedTarget(QPointF(-adjustedSource.width() / 2.0, -adjustedSource.height() / 2.0), fixedSize); return drawInternal(adjustedTarget, pPainter, adjustedSource); } else if (m_drawMode == STRETCH_ASPECT) { qreal sx = targetRect.width() / sourceRect.width(); qreal sy = targetRect.height() / sourceRect.height(); // Adjust the scale so that the scaling in both axes is equal. if (sx != sy) { qreal scale = math_min(sx, sy); qreal scaledWidth = scale * sourceRect.width(); qreal scaledHeight = scale * sourceRect.height(); QRectF adjustedTarget(-scaledWidth / 2.0, -scaledHeight / 2.0, scaledWidth, scaledHeight); return drawInternal(adjustedTarget, pPainter, sourceRect); } else { return drawInternal(targetRect, pPainter, sourceRect); } } else if (m_drawMode == STRETCH) { return drawInternal(targetRect, pPainter, sourceRect); } else if (m_drawMode == TILE) { // TODO(XXX): What's the right behavior here? Draw the first tile at the // center point and then tile all around it based on that? return drawInternal(targetRect, pPainter, sourceRect); } } void Paintable::drawInternal(const QRectF& targetRect, QPainter* pPainter, const QRectF& sourceRect) { // qDebug() << "Paintable::drawInternal" << DrawModeToString(m_draw_mode) // << targetRect << sourceRect; if (m_pPixmap) { if (m_drawMode == TILE) { // TODO(rryan): Using a source rectangle doesn't make much sense // with tiling. Ignore the source rect and tile our natural size // across the target rect. What's the right general behavior here? // NOTE(rryan): We round our target/source rectangles to the nearest // pixel for raster images. pPainter->drawTiledPixmap(targetRect.toRect(), *m_pPixmap, QPoint(0,0)); } else { // NOTE(rryan): We round our target/source rectangles to the nearest // pixel for raster images. pPainter->drawPixmap(targetRect.toRect(), *m_pPixmap, sourceRect.toRect()); } } else if (m_pSvg) { if (m_drawMode == TILE) { qWarning() << "Tiled SVG should have been rendered to pixmap!"; } else { // NOTE(rryan): QSvgRenderer render does not clip for us -- it // applies a world transformation using viewBox and renders the // entire SVG to the painter. We save/restore the QPainter in case // there is an existing clip region (I don't know of any Mixxx code // that uses one but we may in the future). PainterScope PainterScope(pPainter); pPainter->setClipping(true); pPainter->setClipRect(targetRect); m_pSvg->setViewBox(sourceRect); m_pSvg->render(pPainter, targetRect); } } } // static QString Paintable::getAltFileName(const QString& fileName) { // Detect if the alternate image file exists and, if it does, // return its path instead QStringList temp = fileName.split('.'); if (temp.length() != 2) { return fileName; } QString newFileName = temp[0] + QLatin1String("@2x.") + temp[1]; QFile file(newFileName); if (QFileInfo(file).exists()) { return newFileName; } else { return fileName; } }