В прошлой статье был рассмотрен пример того, как сохранить объекты графической сцены в файл SVG, а потом мы смогли открыть его в CorelDraw. А теперь попробуем этот же самый файл открыть и восстановить графические объекты в QGraphicsScene .
Отмечу, что мы не будем использовать класс QSvgRenderer для этого по той причине, что он без проблем поместит содержимое файла SVG на графическую сцену, НО это будет один единый графический объект, а если Вам нужно, чтобы он восстановился в качестве отдельных графических объектов, например, QGraphicsItem , то необходимо будет парсить файл SVG изготавливать из него все графические объекты.
Поскольку файл SVG имеет структуру XML-формата, то разобрать его не представит никакого труда с помощью классов семейства QDomDocument.
Структура проекта
В качестве примера, будет использоваться проект из предыдущей статьи, но будет разбавлен дополнительным классом с двумя статическими методами.
- SVGExample.pro - профайл проекта;
- svgreader.h - заголовочный файл парсера SVG;
- svgreader.cpp - файл исходных кодов парсера SVG;
- mainwindow.h - заголовочный файл главного окна приложения;
- mainwindow.cpp - файл исходных кодов главного окна приложения;
- mainwindow.ui - файл формы главного окна приложения;
- main.cpp - стартовый файл исходных кодов приложения.
Структура SVG файла
Поскольку нам потребуется парсить данный файл, то заглянем в его внутренности.
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <svg width="141.111mm" height="141.111mm" viewBox="0 0 400 400" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.2" baseProfile="tiny"> <title>SVG Example</title> <desc>File created by SVG Example</desc> <defs> </defs> <g fill="none" stroke="black" stroke-width="1" fill-rule="evenodd" stroke-linecap="square" stroke-linejoin="bevel" > <g fill="#ff0000" fill-opacity="1" stroke="#000000" stroke-opacity="1" stroke-width="2" stroke-linecap="square" stroke-linejoin="bevel" transform="matrix(1,0,0,1,0,0)" font-family="MS Shell Dlg 2" font-size="8.25" font-weight="400" font-style="normal" > <rect x="10" y="50" width="100" height="50"/> </g> </g>
Итак, нас интересуют теги svg, g, rect. В теге svg содержатся размеры графической сцены - это viewBox. В теге rect содержатся размеры прямоугольника, а в теге g , в который обёрнут тег rect содержатся цвета заливки и абриса прямоугольника, а также толщина абриса. Их и нужно будет спарсить.
mainwindow.ui
В данном файле будет добавлена только кнопка load, по сигналу от которой откроем диалог выбора файла и запустим открытие нашего файла SVG.
SVGExample.pro
Обращаю ваше внимание на то, что в профайле проекта нужно будет подключить модуль xml.
QT += xml
svgreader.h
Ничем не примечательный заголовочный файл с парой статических методов в классе.
#ifndef SVGREADER_H #define SVGREADER_H #include <QList> #include <QGraphicsRectItem> class SvgReader { public: SvgReader(); static QList<QGraphicsRectItem *> getElements(const QString filename); static QRectF getSizes(const QString filename); }; #endif // SVGREADER_H
svgreader.cpp
А вот здесь и кроется вся магия. В двух выше приведённых методах будем получать список прямоугольников и размеры графической сцены.
#include "svgreader.h" #include <QPen> #include <QFile> #include <QMessageBox> #include <QDomDocument> #include <QStringList> SvgReader::SvgReader() { } QList<QGraphicsRectItem *> SvgReader::getElements(const QString filename) { QList<QGraphicsRectItem *> rectList; // Объявим в стеке список прямоугольников QDomDocument doc; // объект документа QFile file(filename); // Открываем наш SVG-файл // Если он не открылся или не удалось передать содержимое в QDocDocument if (!file.open(QIODevice::ReadOnly) || !doc.setContent(&file)) return rectList; // то возвратим список, но пустой // Ищем в документе все объекты с тегом g QDomNodeList gList = doc.elementsByTagName("g"); // Начинаем их перебирать for (int i = 0; i < gList.size(); i++) { QDomNode gNode = gList.item(i); // Выделяем из списка ноду QDomElement rectangle = gNode.firstChildElement("rect"); // И ищем в ней элемент c тегом rect // Если полученный элементы не нулевой, то if (rectangle.isNull()){ continue; } else { // начинаем формировать прямоугольник QGraphicsRectItem *rect = new QGraphicsRectItem(); // Этот флаг делает объект перемещаемым, потребуется для проверки rect->setFlag(QGraphicsItem::ItemIsMovable); // Забираем размеры из тега rect QDomElement gElement = gNode.toElement(); rect->setRect(rectangle.attribute("x").toInt(), rectangle.attribute("y").toInt(), rectangle.attribute("width").toInt(), rectangle.attribute("height").toInt()); /* Забираем из элемента ноды gNode параметры цветов * да да да... именно из gNode, а не из rectangle. Эти параметры храняться в теге g * */ QColor fillColor(gElement.attribute("fill", "#ffffff")); // цвет заливки fillColor.setAlphaF(gElement.attribute("fill-opacity","0").toFloat()); rect->setBrush(QBrush(fillColor)); // а также цвет и толщина абриса QColor strokeColor(gElement.attribute("stroke", "#000000")); strokeColor.setAlphaF(gElement.attribute("stroke-opacity").toFloat()); rect->setPen(QPen(strokeColor,gElement.attribute("stroke-width", "0").toInt())); rectList.append(rect); // добавляем прямоугольник в список } } file.close(); return rectList; } QRectF SvgReader::getSizes(const QString filename) { QDomDocument doc; // инициализируем в стеке объект QDomDocument QFile file(filename); // Открываем наш SVG-файл // Если он не открылся или не удалось передать содержимое в QDocDocument if (!file.open(QIODevice::ReadOnly) || !doc.setContent(&file)) return QRectF(0,0,200,200); // то возвратим значения для сцены по умолчанию /* Далее забираем список элементов с тегом svg. * В случае, если список элементов будет не пустой, * то заберём размеры графической сцены. * */ QDomNodeList list = doc.elementsByTagName("svg"); if(list.length() > 0) { QDomElement svgElement = list.item(0).toElement(); QStringList parameters = svgElement.attribute("viewBox").split(" "); return QRectF(parameters.at(0).toInt(), parameters.at(1).toInt(), parameters.at(2).toInt(), parameters.at(3).toInt()); } return QRectF(0,0,200,200); }
mainwindow.h
Здесь добавился только слот для реакции на сигнал от кнопки открытия файла.
#ifndef MAINWINDOW_H #define MAINWINDOW_H #include <QMainWindow> #include <QGraphicsScene> #include <QGraphicsRectItem> #include <QSvgGenerator> #include <QFileDialog> #include <QPainter> namespace Ui { class MainWindow; } class MainWindow : public QMainWindow { Q_OBJECT public: explicit MainWindow(QWidget *parent = 0); ~MainWindow(); private slots: void on_saveButton_clicked(); void on_loadButton_clicked(); private: Ui::MainWindow *ui; QGraphicsScene *scene; // Графическая сцена QString path; // Путь сохранения файла }; #endif // MAINWINDOW_H
mainwindow.cpp
Приведу в этом файле только содержимое относящееся непосредственно к разбору SVG файла. В слоте обработки нажатия кнопки выбирается с помощью диалогового окна файл и разбирается на графические объекты.
#include "mainwindow.h" #include "ui_mainwindow.h" #include "svgreader.h" MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); scene = new QGraphicsScene(); ui->graphicsView->setScene(scene); scene->setSceneRect(0,0,400,400); } MainWindow::~MainWindow() { delete ui; } void MainWindow::on_saveButton_clicked() { // Код из предыдущего урока по работе с SVG } void MainWindow::on_loadButton_clicked() { QString newPath = QFileDialog::getOpenFileName(this, trUtf8("Open SVG"), path, tr("SVG files (*.svg)")); if (newPath.isEmpty()) return; path = newPath; scene->clear(); scene->setSceneRect(SvgReader::getSizes(path)); // Зададим размеры графической сцены // Установим на графическую сцену объекты, получив их с помощью метода getElements foreach (QGraphicsRectItem *item, SvgReader::getElements(path)) { QGraphicsRectItem *rect = item; scene->addItem(rect); } }
Итог
В результате вы сможете разобрать сохранённый Вами SVG файл, чтобы забрать из него хотя бы прямоугольники. Если вы хотите забирать и все остальные объекты, то придётся изрядно попотеть и написать парсер для все этих остальных объектов по аналогии с прямоугольником. Единственное, что хочу отметить, не пытайтесь с помощью этого кода открыть файл, созданный изначально в CorelDraw, дело в том, что версии структуры файла SVG тоже сильно отличаются. И данный код предназначен для парсинга файла, который был создан в прошлом уроке, а структура генерируемых файлов пакетом CorelDraw несколько отличается и не будет прочитана полностью.
Подробнее вы можете с этим ознакомиться в видеоуроке. А проект, объединивший оба урока скачать по ссылке: SvgExample
Здравствуйте!
Там в коде у меня есть:
Для формирования задал объект QGraphiscPathItem
А кажется понял, я должен из атрибутов path сформировать заново элементы Ellipse, просто нужно правильно подобрать эти атрибуты.....
Ну да. Там должны быть координаты, их нужно пропарсить и по ним построить Path, либо преобразовать их в эллипс. Вообще, кое-что кривовато переносится в SVG местами.
How can I open another polygon type such as circle(ellipse) ?
The principle will be similar. You need to research content of svg file and make parsing of needed tag.
Thank You sir, So how can I change this method
For what You want to use another class instead of QRectF?
Sir,I tried your code for open ellipse item.but my program not open ellipse item.what should I need to do?
I think You have another version of SVG file. First, need to see content of SVG file. It is simple XML-format, therefore just need to research content.
Sir I post is as a topic,please help me to solve this problem
Скажите пожалуйста, как пользоваться QSvgRenderer и загружать целиком в QGraphicsScene ?
Если делаю так, то получаю белый экран:
А если делаю с помощью вашего класса, то тоже получаю белый экрак.
Рисую на сцене так: Qt/C++ - Урок 021. Рисование мышью в Qt
Вот так целиком добавлять можно