Реклама

Qt/C++ - Урок 070. Обрезка изображения с помощью QGraphicsScene

РуководствоQtqgraphicsscene, Qt, Image, crop1846

Напишем небольшое приложение, которое позволит обрезать изображение с помощью QGraphicsScene . При этом обрезка изображения будет производиться так, чтобы получался квадрат. То есть, чтобы изображение было всегда квадратным (Добавим этот функционал, чтобы просто было интереснее).

В приложении будет добавлена графическая сцена, на которую через QFileDialog будет добавляться изображение. Также будет QLabel, в который будет добавляться вырезанная часть изображения. Обрезка изображения будет производиться с помощью объекта QGraphicsRectItem .

Механизм вырезания следующий:

  1. При левом клике мышью по изображению создаётся квадрат выделения.
  2. При зажатой ЛКМ масштабируем квадрат.
  3. При отпускании уничтожаем объект выделения, вырезав с графической сцена часть изображения.

При отпускании ЛКМ будут взяты параметры выделения, а именно QRect , с помощью которого можно будет вырезать требуемую область изображения.

Приложение будет выглядеть следующим образом:

Структура проекта

Проект создан в IDE CLion с использованием системы сборки CMake.

  • CMakeLists.txt
  • main.cpp
  • EWidget.h - заголовочный файл окна приложения
  • EWidget.cpp - файл исходных кодов окна приложения
  • ClipScene.h - заголовочный файл графической сцена с функционалом вырезания
  • ClipScene.cpp - файл исходных кодов графической сцены

CMakeLists.txt

cmake_minimum_required(VERSION 3.8)
project(ClipImage)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_AUTOMOC ON)

find_package(Qt5Core REQUIRED)
find_package(Qt5Gui REQUIRED)
find_package(Qt5Widgets REQUIRED)

set(SOURCE_FILES main.cpp EWidget.cpp EWidget.h ClipScene.cpp ClipScene.h)
add_executable(${PROJECT_NAME} ${SOURCE_FILES})

target_link_libraries(${PROJECT_NAME} Qt5::Core Qt5::Gui Qt5::Widgets)

main.cpp

#include <QApplication>
#include "EWidget.h"


int main(int argc, char *argv[])
{
    QApplication app(argc, argv);

    EWidget widget;
    widget.show();

    return app.exec();
}

EWidget.h

В окне приложения необходимо создать слоты для добавления изображения на графическую сцену, а также для добавления обрезанного изображения на лейбл в приложении.

Также здесь объявлены виджеты и лейауты для вёрстки.

#pragma once

#include <QtWidgets/QWidget>

class QGridLayout;
class QPushButton;
class QGraphicsView;
class QLabel;
class ClipScene;

class EWidget : public QWidget
{
    using BaseClass = QWidget;

    Q_OBJECT

public:
    EWidget(QWidget* parent = nullptr);

private slots:
    void onAddFile();                           // Слот добавления изображения в приложение
    void onClippedImage(const QPixmap& pixmap); // Слот принимающий обрезанную область приложения

private:
    QGridLayout* m_gridLayout;
    QPushButton* m_pushButton;
    QGraphicsView* m_graphicsView;
    QLabel* m_clippedLabel;         // Лейбл, в который будет помещаться обрезанное изображение
    ClipScene* m_clipScene;         // Графическая сцена в которой реализован функционал по обрезке изображения
};

EWidget.cpp

#include "EWidget.h"

#include "ClipScene.h"

#include <QtWidgets/QGridLayout>
#include <QtWidgets/QPushButton>
#include <QtWidgets/QFileDialog>
#include <QtWidgets/QLabel>
#include <QtWidgets/QGraphicsView>

EWidget::EWidget(QWidget* parent) :
    BaseClass(parent)
{
    m_gridLayout = new QGridLayout(this);
    m_graphicsView = new QGraphicsView(this);
    m_gridLayout->addWidget(m_graphicsView), 0, 0;
    m_pushButton = new QPushButton("Add file", this);
    m_clippedLabel = new QLabel(this);
    m_gridLayout->addWidget(m_clippedLabel, 0, 1);
    m_gridLayout->addWidget(m_pushButton, 1, 0);
    m_clipScene = new ClipScene(this);
    m_graphicsView->setScene(m_clipScene);

    // Коннекты к слотам для добавления изображения в приложение и для добавления изображения на лейбл
    connect(m_pushButton, &QPushButton::clicked, this, &EWidget::onAddFile);
    connect(m_clipScene, &ClipScene::clippedImage, this, &EWidget::onClippedImage);

    resize(640, 480);
}

void EWidget::onAddFile()
{
    QString imagePath = QFileDialog::getOpenFileName(this, "Open Image File", QString(), tr("Image (*.png *.jpg)"));
    m_clipScene->setImage(imagePath);
}

void EWidget::onClippedImage(const QPixmap& pixmap)
{
    m_clippedLabel->setPixmap(pixmap);
}

ClipScene.h

Изменение размеров области выделения будет производиться от точки нажатия ЛКМ. Для этого необходимо переопределить следующие методы:

  • mousePressEvent
  • mouseMoveEvent
  • mouseReleaseEvent

Также объявим переменную, которая будет сообщать о том, что в данный момент движения мыши ЛКМ зажата.

Помимо этого объявим указатели на QGrpaphicsRectItem , который будет играть роль выделения, и QGraphicsPixmapItem , который будет изображением, из которого вырезается область.

#pragma once


#include <QtWidgets/QGraphicsScene>

class QGraphicsSceneMouseEvent;
class QGraphicsPixmapItem;
class QGraphicsRectItem;

class ClipScene : public QGraphicsScene
{
    Q_OBJECT
    Q_PROPERTY(QPointF previousPosition READ previousPosition WRITE setPreviousPosition NOTIFY previousPositionChanged)

public:
    ClipScene(QObject* parent);
    QPointF previousPosition() const;
    // Метод для замены изображения в QGraphicsScene
    void setImage(const QString& filePath);

signals:
    void previousPositionChanged(const QPointF previousPosition);
    void clippedImage(const QPixmap& pixmap);  // Сигнал, который передаёт вырезанную область в окно приложения для установки его в лейбл

public slots:
    void setPreviousPosition(const QPointF previousPosition);

protected:
    virtual void mousePressEvent(QGraphicsSceneMouseEvent* event) override;
    virtual void mouseMoveEvent(QGraphicsSceneMouseEvent* event) override;
    virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent* event) override;


private:
    QGraphicsRectItem* m_selection          {nullptr};
    QGraphicsPixmapItem* m_currentImageItem {nullptr};
    QPointF m_previousPosition;
    bool m_leftMouseButtonPressed           {false};
};

ClipScene.cpp

#include "ClipScene.h"

#include <QGraphicsSceneMouseEvent>
#include <QtWidgets/QGraphicsPixmapItem>
#include <QtWidgets/QGraphicsRectItem>


ClipScene::ClipScene(QObject* parent) : QGraphicsScene(parent)
{

}

void ClipScene::mousePressEvent(QGraphicsSceneMouseEvent* event)
{
    if (event->button() & Qt::LeftButton)
    {
        // При зажатой левой кнопки мыши, запоминаем позицию 
        m_leftMouseButtonPressed = true;
        setPreviousPosition(event->scenePos());

        // создаём квадрат выделения
        m_selection = new QGraphicsRectItem();
        m_selection->setBrush(QBrush(QColor(158,182,255,96)));
        m_selection->setPen(QPen(QColor(158,182,255,200),1));
        // добавляем его на графическую сцену
        addItem(m_selection);
    }

    QGraphicsScene::mousePressEvent(event);
}

void ClipScene::mouseMoveEvent(QGraphicsSceneMouseEvent* event)
{
    if (m_leftMouseButtonPressed)
    {
        // формируем область выделения при движениюю мышью при зажатой ЛКМ
        auto dx = event->scenePos().x() - m_previousPosition.x();
        auto dy = event->scenePos().y() - m_previousPosition.y();
        auto resultDelta = qMax(qAbs(dx), qAbs(dy));
        m_selection->setRect((dx > 0) ? m_previousPosition.x() : m_previousPosition.x() - resultDelta,
                           (dy > 0) ? m_previousPosition.y() : m_previousPosition.y() - resultDelta,
                           qAbs(resultDelta),
                           qAbs(resultDelta));
    }
    QGraphicsScene::mouseMoveEvent(event);
}

void ClipScene::mouseReleaseEvent(QGraphicsSceneMouseEvent* event)
{
    if (event->button() & Qt::LeftButton)
    {
        m_leftMouseButtonPressed = false;

        // При отпускании ЛКМ формируем обрезанную область
        QRect selectionRect = m_selection->boundingRect().toRect();
        clippedImage(m_currentImageItem->pixmap().copy(selectionRect));
        delete m_selection;
    }
    QGraphicsScene::mouseReleaseEvent(event);
}

void ClipScene::setPreviousPosition(const QPointF previousPosition)
{
    if (m_previousPosition == previousPosition)
        return;

    m_previousPosition = previousPosition;
    emit previousPositionChanged(m_previousPosition);
}

QPointF ClipScene::previousPosition() const
{
    return m_previousPosition;
}

void ClipScene::setImage(const QString& filePath)
{
    if (m_currentImageItem)
    {
        this->removeItem(m_currentImageItem);
    }
    m_currentImageItem = new QGraphicsPixmapItem(QPixmap(filePath));
    addItem(m_currentImageItem);
}

Заключение

Ссылка на скачивание проекта

Реклама

Комментарии

Комментарии

Только авторизованные пользователи могут оставлять комментарии.
Пожалуйста, Авторизуйтесь или Зарегистрируйтесь
  • BoostEX
  • 17 августа 2017 г. 16:45

C++ - Тест 001. Первая программа и типы данных

  • Результат - 73 баллов
  • Nordman
  • 15 августа 2017 г. 20:40

C++ - Тест 005. Структуры и Классы

  • Результат - 66 баллов

C++ - Тест 002. Константы

  • Результат - 33 баллов
Последние комментарии
  • EVILEG
  • 17 августа 2017 г. 18:33

Qt/C++ - Урок 069. Шифрование методом XOR

Не обратил внимания на это, Проверял с большим текстом.. По идее не должно.

Qt/C++ - Урок 069. Шифрование методом XOR

Шифрует/дешифрует текст от 8 символов, так и должно быть?

  • EVILEG
  • 15 августа 2017 г. 20:32

Qt/C++ - Урок 048. QThread - работа с потоками с помощью moveToThread

Нууу... тут уже вопрос к самому Qt4.8. Если честно, идей нет, да и копаться в deprecated коде желания тоже нет.

  • t000r
  • 15 августа 2017 г. 19:49

Qt/C++ - Урок 048. QThread - работа с потоками с помощью moveToThread

В qt5.6 всё нормально заработало. С 4.8 - нет

  • EVILEG
  • 15 августа 2017 г. 17:44

Qt/C++ - Урок 050. Логирование событий Qt приложения в текстовый файл

Я полистал информацию в интернетах, вроде как кто-то пытается подружить его с Qt5, но успешных результатов не нашёл. Да и на сайте как-то не заметно информации о том, что конкретно ему нужно, ...

Сейчас обсуждают на форуме

Сборка Qt / C++ проекта под windows и linux

вы имели ввиду это? если да, то как то не работает((( #include <iostream>#include <bitset> // заголовочный файл битовых полей#include <iomanip> // для манип...

  • alex_lip
  • 17 августа 2017 г. 19:11

Я только учусь..(как правильно присвоить значение объекту другого класса)

А что вы думаете про директиву friend ? class A { friend void B::changeValue(); private: int _value;};class B { void changeValue() { a-&...

  • EVILEG
  • 16 августа 2017 г. 13:38

Перевод кодировки строки из windows 1251 в Utf-8

Здесь необходимо использовать QTextCodec. Вещь это очень хитрая в том плане, что объект этого класса необходимо создавать с определённой кодировкой. Поскольку он будет гонять данные от заданной код...

  • EVILEG
  • 15 августа 2017 г. 17:09

Переключение между Qt::WindowMaximized и Qt::WindowNoState при фиксированном размере окна.

Согласен - это велосипед, но это гораздо меньше, чем отключить оформление окна и написать своё оформление )) Например, как здесь .

  • EVILEG
  • 13 августа 2017 г. 13:51

SQLITE speed up

Заполняете в цикле из объекта query? Можете показать тот кусок кода, где выполняете эти манипуляции? Ускорить код разве только некоторым манипуляциями с указателями или ссылками, н...