В прошлой статье был рассмотрен пример того, как сохранить объекты графической сцены в файл 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
Вот так целиком добавлять можно