#include #include #include #include #include #include #include #include "svgview.h" SvgViewWorker::SvgViewWorker(QObject *parent) : QObject(parent) , m_dpiRatio(qApp->screens().first()->devicePixelRatio()) { } QRect SvgViewWorker::viewBox() const { QMutexLocker lock(&m_mutex); return m_renderer.viewBox(); } QString SvgViewWorker::loadData(const QByteArray &data) { QMutexLocker lock(&m_mutex); m_renderer.load(data); if (!m_renderer.isValid()) { return m_renderer.errorString(); } return QString(); } QString SvgViewWorker::loadFile(const QString &path) { QMutexLocker lock(&m_mutex); m_renderer.load(path); if (!m_renderer.isValid()) { return m_renderer.errorString(); } return QString(); } void SvgViewWorker::render(const QSize &viewSize) { Q_ASSERT(QThread::currentThread() != qApp->thread()); QMutexLocker lock(&m_mutex); if (m_renderer.isEmpty()) { return; } const auto s = m_renderer.defaultSize().scaled(viewSize, Qt::KeepAspectRatio); QImage img(s * m_dpiRatio, QImage::Format_ARGB32_Premultiplied); img.fill(Qt::transparent); QPainter p; p.begin(&img); p.setRenderHint(QPainter::Antialiasing); m_renderer.render(&p); p.end(); img.setDevicePixelRatio(m_dpiRatio); emit rendered(img); } static QImage genCheckedTexture() { int l = 20; QImage pix = QImage(l, l, QImage::Format_RGB32); int b = pix.width() / 2.0; pix.fill(QColor("#c0c0c0")); QPainter p; p.begin(&pix); p.fillRect(QRect(0,0,b,b), QColor("#808080")); p.fillRect(QRect(b,b,b,b), QColor("#808080")); p.end(); return pix; } SvgView::SvgView(QWidget *parent) : QFrame(parent) , m_checkboardImg(genCheckedTexture()) , m_worker(new SvgViewWorker()) , m_resizeTimer(new QTimer(this)) { setAcceptDrops(true); setMinimumSize(10, 10); QThread *th = new QThread(this); m_worker->moveToThread(th); th->start(); const auto *screen = qApp->screens().first(); m_dpiRatio = screen->devicePixelRatio(); connect(m_worker, &SvgViewWorker::rendered, this, &SvgView::onRendered); m_resizeTimer->setSingleShot(true); connect(m_resizeTimer, &QTimer::timeout, this, &SvgView::requestUpdate); } SvgView::~SvgView() { QThread *th = m_worker->thread(); th->quit(); th->wait(10000); delete m_worker; } void SvgView::init() { ResvgRenderer::initLog(); } void SvgView::setFitToView(bool flag) { m_isFitToView = flag; requestUpdate(); } void SvgView::setBackgound(SvgView::Backgound backgound) { m_backgound = backgound; update(); } void SvgView::setDrawImageBorder(bool flag) { m_isDrawImageBorder = flag; update(); } void SvgView::loadData(const QByteArray &ba) { const QString errMsg = m_worker->loadData(ba); afterLoad(errMsg); } void SvgView::loadFile(const QString &path) { const QString errMsg = m_worker->loadFile(path); afterLoad(errMsg); } void SvgView::afterLoad(const QString &errMsg) { m_img = QImage(); if (errMsg.isEmpty()) { m_isHasImage = true; requestUpdate(); } else { emit loadError(errMsg); m_isHasImage = false; update(); } } void SvgView::drawSpinner(QPainter &p) { const int outerRadius = 20; const int innerRadius = outerRadius * 0.45; const int capsuleHeight = outerRadius - innerRadius; const int capsuleWidth = capsuleHeight * 0.35; const int capsuleRadius = capsuleWidth / 2; for (int i = 0; i < 12; ++i) { QColor color = Qt::black; color.setAlphaF(1.0f - (i / 12.0f)); p.setRenderHint(QPainter::Antialiasing); p.setPen(Qt::NoPen); p.setBrush(color); p.save(); p.translate(width()/2, height()/2); p.rotate(m_angle - i * 30.0f); p.drawRoundedRect(-capsuleWidth * 0.5, -(innerRadius + capsuleHeight), capsuleWidth, capsuleHeight, capsuleRadius, capsuleRadius); p.restore(); } } void SvgView::paintEvent(QPaintEvent *e) { QPainter p(this); const auto r = contentsRect(); p.setClipRect(r); switch (m_backgound) { case Backgound::None : break; case Backgound::White : { p.fillRect(contentsRect(), Qt::white); } break; case Backgound::CheckBoard : { p.fillRect(contentsRect(), QBrush(m_checkboardImg)); } break; } if (m_img.isNull() && !m_timer.isActive()) { p.setPen(Qt::black); p.drawText(rect(), Qt::AlignCenter, "Drop an SVG image here."); } else if (m_timer.isActive()) { drawSpinner(p); } else { const QRect imgRect(0, 0, m_img.width() / m_dpiRatio, m_img.height() / m_dpiRatio); p.translate(r.x() + (r.width() - imgRect.width())/ 2, r.y() + (r.height() - imgRect.height()) / 2); p.drawImage(0, 0, m_img); if (m_isDrawImageBorder) { p.setRenderHint(QPainter::Antialiasing, false); p.setPen(Qt::green); p.setBrush(Qt::NoBrush); p.drawRect(imgRect); } } QFrame::paintEvent(e); } void SvgView::dragEnterEvent(QDragEnterEvent *event) { event->accept(); } void SvgView::dragMoveEvent(QDragMoveEvent *event) { event->accept(); } void SvgView::dropEvent(QDropEvent *event) { const QMimeData *mime = event->mimeData(); if (!mime->hasUrls()) { event->ignore(); return; } for (const QUrl &url : mime->urls()) { if (!url.isLocalFile()) { continue; } QString path = url.toLocalFile(); QFileInfo fi = QFileInfo(path); if (fi.isFile()) { QString suffix = fi.suffix().toLower(); if (suffix == "svg" || suffix == "svgz") { loadFile(path); } else { QMessageBox::warning(this, tr("Warning"), tr("You can drop only SVG and SVGZ files.")); } } } event->acceptProposedAction(); } void SvgView::resizeEvent(QResizeEvent *) { m_resizeTimer->start(200); } void SvgView::timerEvent(QTimerEvent *event) { if (event->timerId() == m_timer.timerId()) { m_angle = (m_angle + 30) % 360; update(); } else { QWidget::timerEvent(event); } } void SvgView::requestUpdate() { if (!m_isHasImage) { return; } const auto s = m_isFitToView ? size() : m_worker->viewBox().size(); if (s * m_dpiRatio == m_img.size()) { return; } m_timer.start(100, this); // Run method in the m_worker thread scope. QTimer::singleShot(1, m_worker, [=](){ m_worker->render(s); }); } void SvgView::onRendered(const QImage &img) { m_timer.stop(); m_img = img; update(); }