Chap24-Reconnect-LockPrecisionOptimization-AvatarEditBox

Reconnect-LockPrecisionOptimization-AvatarEditBox

断线重连

为了是我们的服务器更具有可恢复性,健壮性,我们必须考虑可能的网络波动导致的短连或者长时间为连接导致自动断连。为此我们设计了断线重连,包括mysql和redis。

实际上这两者的断线重连逻辑是一样的,因此我们只演示mysql的断线重连。

我们新添加三个成员:

private:
    int64_t _last_operate_time;
    int _failed_count;
	std::thread _check_thread;

构造函数中,我们一一构造连接对象,如果有未成功连接的就记录下来(_failed_count++)

MysqlPool::MysqlPool(const std::string &url, const std::string &user,
                     const std::string &password, const std::string &schedma,
                     const std::string &port, int poolSize)
    : _url(url)
    , _user(user)
    , _password(password)
    , _schedma(schedma)
    , _port(port)
    , _poolSize(poolSize)
    , _stop(false)
    , _last_operate_time()
    , _failed_count(0) {
    for (std::size_t i = 0; i < _poolSize; ++i) {
        try {
            auto conn = std::make_unique<mysqlpp::Connection>();
            if (conn->connect(_schedma.c_str(), _url.c_str(), _user.c_str(),
                              _password.c_str(), std::stoi(_port))) {
                _connections.push(std::move(conn));
            } else {
                SPDLOG_ERROR("Failed To Create Database Connection: {}",
                             conn->error());
                _failed_count++;
            }
        } catch (const mysqlpp::Exception &e) {
            SPDLOG_ERROR("Failed to connect to mysql:{}", e.what());
            _failed_count++;
        }
    }
    auto currentTime = std::chrono::system_clock::now().time_since_epoch();
    long long timestamp =
        std::chrono::duration_cast<std::chrono::seconds>(currentTime).count();
    _last_operate_time = timestamp;

    if (_connections.size() < _poolSize) {
        SPDLOG_WARN("Connection Pool Initialized With Only {}/{} Connections",
                    _connections.size(), _poolSize);
    } else {
        SPDLOG_INFO("Mysql Connection Pool Initialized");
    }

    _check_thread = std::thread([this]() {
        int count = 0;
        while (!_stop) {
            if (count >= 60 * 5) {
                count = 0;
                checkConnection();
            }
            count++;
            std::this_thread::sleep_for(std::chrono::seconds(1));
        }
    });
    _check_thread.detach();
}

同时我们注意到了一个检测线程_check_thread,逻辑是,每60*5s(即5分钟)进行一次检测。至于为什么这么长,因为就目前的组件的连接性而言,稳定性是可以信赖的。而比如mysql默认长时间无沟通断开连接的时间是8小时。因此5分钟是经过考量,既不影响性能,也能及时检测连接的一个时间。

这里呢之所以没过1s休眠一次而不是直接休眠5分钟,是要考虑_stop的情况,如果主线程退出,最长需要等待5分钟,检测线程才能退出。因此没过1s检测一次实际也没有太大的损耗,但是可以更快的检测退出。

void MysqlPool::checkConnection() {
    std::lock_guard<std::mutex> lock(_mutex);
    auto currentTime = std::chrono::system_clock::now().time_since_epoch();

    long long timestamp =
        std::chrono::duration_cast<std::chrono::seconds>(currentTime).count();

    if (timestamp - _last_operate_time >= 30) {
        _last_operate_time = timestamp;
        if (_connections.empty() && !_failed_count) {
            return;
        }
        int target = _connections.size();
        for (int i = 0; i < target; ++i) {
            bool health = true;
            auto conn = std::move(_connections.front());
            _connections.pop();
            try {
                mysqlpp::Query query = conn->query();
                query << "SELECT 1";
                query.parse();
                auto res = query.store();
            } catch (const std::exception &e) {
                SPDLOG_ERROR("MySQL++ exception: {}", e.what());
                _failed_count++;
                health = false;
            }
            if (health) {
                _connections.push(std::move(conn));
                _cv.notify_one();
            }
        }

        while (_failed_count > 0) {
            auto b_ok = Reconnect();
            if (b_ok) {
                _failed_count--;
            } else {
                break;
            }
        }
    }
}

如上,我们检测的时候,先看上次操作的时间和当前时间戳对比,如果超过30s,我们就进行一次“ping”,如果失败了使得_failed_count++.当_failed_count不0,就会进行重试。

bool MysqlPool::Reconnect() {
    SPDLOG_INFO("RETRY");
    try {
        auto conn = std::make_unique<mysqlpp::Connection>();
        if (conn->connect(_schedma.c_str(), _url.c_str(), _user.c_str(),
                          _password.c_str(), std::stoi(_port))) {
            _connections.push(std::move(conn));
            _cv.notify_one();
            return true;
        }
        SPDLOG_WARN("Reconnect to MYSQL failed:{}", conn->error());
        return false;
    } catch (const std::exception &e) {
        SPDLOG_WARN("Reconnect to MYSQL failed:{}", e.what());
        return false;
    }
}

锁的精度优化

在我们的检测线程中,还是其他的操作,直接锁住了整个队列

std::lock_guard<std::mutex> lock(_mutex);

实际上这样锁的精度太低。这里提供一种思路。

我们队列中不存放std::unique_ptr<mysqlpp::Connection>

std::queue<std::unique_ptr<mysqlpp::Connection>> _connections;

而是存放我们自定义的类SqlConnection

class SqlConnection
{
public:
    SqlConnection(mysqlpp::Connection*conn,int64_t last_time)
        : _conn(conn)
        , _last_time(last_time){}
    std::unique_ptr<mysqlpp::Connection>_conn;
    int64_t _last_time;
}

这样做的目的呢,就是我们是根据每个连接对象的操作时间差进行重试的。之前我们是锁住整个队列,因为整个队列共享一个时间戳。满足重试条件整个队列的所有对象全部PING

这里我们给每个对象分配一个_last_time.这样我们只需要在循环中锁定一个对象,检测即可。虽然需要循环中获取锁,销毁,获取,销毁。但是允许其他线程进行竞争, 并发能力也提上来了。

void checkConnectionPro() {
    size_t targetCount;
    {
        std::lock_guard<std::mutex> guard(mutex_);
        targetCount = pool_.size();
    }

    size_t processed = 0;

    auto now = std::chrono::system_clock::now().time_since_epoch();
    long long timestamp = std::chrono::duration_cast<std::chrono::seconds>(now).count();

    while (processed < targetCount) {
        std::unique_ptr<SqlConnection> con;
        {
            std::lock_guard<std::mutex> guard(mutex_);
            if (pool_.empty()) {
                break;
            }
            con = std::move(pool_.front());
            pool_.pop();
        }

        bool healthy = true;
        if (timestamp - con->_last_oper_time >= 5) {
            try {
                std::unique_ptr<sql::Statement> stmt(con->_con->createStatement());
                stmt->executeQuery("SELECT 1");
                con->_last_oper_time = timestamp;
            }
            catch (sql::SQLException& e) {
                std::cout << "Error keeping connection alive: " << e.what() << std::endl;
                healthy = false;
                _fail_count++;
            }
        }

        if (healthy)
        {
            std::lock_guard<std::mutex> guard(mutex_);
            pool_.push(std::move(con));
            cond_.notify_one();
        }

        ++processed;
    }

    while (_fail_count > 0) {
        auto b_res = Reconnect(timestamp);
        if (b_res) {
            _fail_count--;
        }
        else {
            break;
        }
    }
}

资料展示以及头像编辑框

我们设计了鼠标悬浮头像两秒出现资料框,点击编辑资料出现编辑框:

image-20251206103247464

image-20251206103149251

在我们点击更换头像之后,出现一个头像编辑框,可以自由的选定区域:

image-20251206103403321

之后资料框,和头像框都将设置为新的内容。

image-20251206103511452

具体头像框的实现,参考了网上的一个插件,原文是分为了多个文件ImageCropperDialog,ImageCropperLabel等。这里我们为了方便,直接组成了一个文件

这里贴出:

//.h

#ifndef IMAGECROPPER_H
#define IMAGECROPPER_H

#include <QWidget>
#include <QDialog>
#include <QPainter>
#include <QLabel>
#include <QPixmap>
#include <QString>
#include <QMessageBox>
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QPushButton>
#include <QPen>


/*******************************************************
 *  Loacl private class, which do image-cropping
 *  Used in class ImageCropper
 *******************************************************/



enum class CropperShape {
    UNDEFINED     = 0,
    RECT          = 1,
    SQUARE        = 2,
    FIXED_RECT    = 3,
    ELLIPSE       = 4,
    CIRCLE        = 5,
    FIXED_ELLIPSE = 6
};

enum class OutputShape {
    RECT    = 0,
    ELLIPSE = 1
};

enum class SizeType {
    fixedSize           = 0,
    fitToMaxWidth       = 1,
    fitToMaxHeight      = 2,
    fitToMaxWidthHeight = 3,
};


class ImageCropperLabel : public QLabel {
    Q_OBJECT
  public:
    ImageCropperLabel(int width, int height, QWidget* parent);

    void setOriginalImage(const QPixmap& pixmap);
    void setOutputShape(OutputShape shape) { outputShape = shape; }
    QPixmap getCroppedImage();
    QPixmap getCroppedImage(OutputShape shape);

    /*****************************************
     * Set cropper's shape
     *****************************************/
    void setRectCropper();
    void setSquareCropper();
    void setEllipseCropper();
    void setCircleCropper();
    void setFixedRectCropper(QSize size);
    void setFixedEllipseCropper(QSize size);
    void setCropper(CropperShape shape, QSize size);    // not recommended

    /*****************************************************************************
     * Set cropper's fixed size
     *****************************************************************************/
    void setCropperFixedSize(int fixedWidth, int fixedHeight);
    void setCropperFixedWidth(int fixedWidht);
    void setCropperFixedHeight(int fixedHeight);

    /*****************************************************************************
     * Set cropper's minimum size
     * default: the twice of minimum of the edge lenght of drag square
     *****************************************************************************/
    void setCropperMinimumSize(int minWidth, int minHeight)
    { cropperMinimumWidth = minWidth; cropperMinimumHeight = minHeight; }
    void setCropperMinimumWidth(int minWidth) { cropperMinimumWidth = minWidth; }
    void setCropperMinimumHeight(int minHeight) { cropperMinimumHeight = minHeight; }

    /*************************************************
     * Set the size, color, visibility of rectangular border
     *************************************************/
    void setShowRectBorder(bool show) { isShowRectBorder = show; }
    QPen getBorderPen() { return borderPen; }
    void setBorderPen(const QPen& pen) { borderPen = pen; }

    /*************************************************
     * Set the size, color of drag square
     *************************************************/
    void setShowDragSquare(bool show) { isShowDragSquare = show; }
    void setDragSquareEdge(int edge) { dragSquareEdge = (edge >= 3 ? edge : 3); }
    void setDragSquareColor(const QColor& color) { dragSquareColor = color; }

    /*****************************************
     *  Opacity Effect
     *****************************************/
    void enableOpacity(bool b = true) { isShowOpacityEffect = b; }
    void setOpacity(double newOpacity) { opacity = newOpacity; }

  signals:
    void croppedImageChanged();

  protected:
    /*****************************************
     * Event
     *****************************************/
    virtual void paintEvent(QPaintEvent *event) override;
    virtual void mousePressEvent(QMouseEvent *e) override;
    virtual void mouseMoveEvent(QMouseEvent *e) override;
    virtual void mouseReleaseEvent(QMouseEvent *e) override;

  private:
    /***************************************
     * Draw shapes
     ***************************************/
    void drawFillRect(QPoint centralPoint, int edge, QColor color);
    void drawRectOpacity();
    void drawEllipseOpacity();
    void drawOpacity(const QPainterPath& path);     // shadow effect
    void drawSquareEdge(bool onlyFourCorners);

    /***************************************
     * Other utility methods
     ***************************************/
    int getPosInCropperRect(const QPoint& pt);
    bool isPosNearDragSquare(const QPoint& pt1, const QPoint& pt2);
    void resetCropperPos();
    void changeCursor();

    enum {
        RECT_OUTSIZD = 0,
        RECT_INSIDE = 1,
        RECT_TOP_LEFT, RECT_TOP, RECT_TOP_RIGHT, RECT_RIGHT,
        RECT_BOTTOM_RIGHT, RECT_BOTTOM, RECT_BOTTOM_LEFT, RECT_LEFT
    };

    const bool ONLY_FOUR_CORNERS = true;

  private:
    QPixmap originalImage;
    QPixmap tempImage;

    bool isShowRectBorder = true;
    QPen borderPen;

    CropperShape cropperShape = CropperShape::UNDEFINED;
    OutputShape  outputShape  = OutputShape::RECT;

    QRect imageRect;     // the whole image area in the label (not real size)
    QRect cropperRect;     // a rectangle frame to choose image area (not real size)
    QRect cropperRect_;     // cropper rect (real size)
    double scaledRate = 1.0;

    bool isLButtonPressed = false;
    bool isCursorPosCalculated = false;
    int  cursorPosInCropperRect = RECT_OUTSIZD;
    QPoint lastPos;
    QPoint currPos;

    bool isShowDragSquare = true;
    int dragSquareEdge = 8;
    QColor dragSquareColor = Qt::white;

    int cropperMinimumWidth = dragSquareEdge * 2;
    int cropperMinimumHeight = dragSquareEdge * 2;

    bool isShowOpacityEffect = false;
    double opacity = 0.6;
};
















class ImageCropperDialogPrivate : public QDialog {
    Q_OBJECT
  public:
    ImageCropperDialogPrivate(const QPixmap& imageIn, QPixmap& outputImage,
                              int windowWidth, int windowHeight,
                              CropperShape shape, QSize cropperSize = QSize()) :
        QDialog(nullptr),  outputImage(outputImage)
    {
        this->setAttribute(Qt::WA_DeleteOnClose, true);
        this->setWindowTitle("Image Cropper");
        this->setMouseTracking(true);
        this->setModal(true);

        imageLabel = new ImageCropperLabel(windowWidth, windowHeight, this);
        imageLabel->setCropper(shape, cropperSize);
        imageLabel->setOutputShape(OutputShape::RECT);
        imageLabel->setOriginalImage(imageIn);
        imageLabel->enableOpacity(true);

        QHBoxLayout* btnLayout = new QHBoxLayout();
        btnOk = new QPushButton("OK", this);
        btnCancel = new QPushButton("Cancel", this);
        btnLayout->addStretch();
        btnLayout->addWidget(btnOk);
        btnLayout->addWidget(btnCancel);

        QVBoxLayout* mainLayout = new QVBoxLayout(this);
        mainLayout->addWidget(imageLabel);
        mainLayout->addLayout(btnLayout);

        connect(btnOk, &QPushButton::clicked, this, [this](){
            this->outputImage = this->imageLabel->getCroppedImage();
            this->close();
        });
        connect(btnCancel, &QPushButton::clicked, this, [this](){
            this->outputImage = QPixmap();
            this->close();
        });
    }

  private:
    ImageCropperLabel* imageLabel;
    QPushButton* btnOk;
    QPushButton* btnCancel;
    QPixmap& outputImage;
};


/*******************************************************************
 *  class ImageCropperDialog
 *      create a instane of class ImageCropperDialogPrivate
 *      and get cropped image from the instance(after closing)
 ********************************************************************/
class ImageCropperDialog : QObject {
  public:
    static QPixmap getCroppedImage(const QString& filename,int windowWidth, int windowHeight,
                                   CropperShape cropperShape, QSize crooperSize = QSize())
    {
        QPixmap inputImage;
        QPixmap outputImage;

        if (!inputImage.load(filename)) {
            QMessageBox::critical(nullptr, "Error", "Load image failed!", QMessageBox::Ok);
            return outputImage;
        }

        ImageCropperDialogPrivate* imageCropperDo =
            new ImageCropperDialogPrivate(inputImage, outputImage,
                                          windowWidth, windowHeight,
                                          cropperShape, crooperSize);
        imageCropperDo->exec();

        return outputImage;
    }
};



#endif // IMAGECROPPER_H

#include "imagecropperdialog.h"

#include <QPainter>
#include <QPainterPath>
#include <QMouseEvent>
#include <QDebug>
#include <QBitmap>

ImageCropperLabel::ImageCropperLabel(int width, int height, QWidget* parent) :
    QLabel(parent)
{
    this->setFixedSize(width, height);
    this->setAlignment(Qt::AlignCenter);
    this->setMouseTracking(true);

    borderPen.setWidth(1);
    borderPen.setColor(Qt::white);
    borderPen.setDashPattern(QVector<qreal>() << 3 << 3 << 3 << 3);
}

void ImageCropperLabel::setOriginalImage(const QPixmap &pixmap) {
    originalImage = pixmap;

    int imgWidth = pixmap.width();
    int imgHeight = pixmap.height();
    int labelWidth = this->width();
    int labelHeight = this->height();
    int imgWidthInLabel;
    int imgHeightInLabel;

    if (imgWidth * labelHeight < imgHeight * labelWidth) {
        scaledRate = labelHeight / double(imgHeight);
        imgHeightInLabel = labelHeight;
        imgWidthInLabel = int(scaledRate * imgWidth);
        imageRect.setRect((labelWidth - imgWidthInLabel) / 2, 0,
                          imgWidthInLabel, imgHeightInLabel);
    }
    else {
        scaledRate = labelWidth / double(imgWidth);
        imgWidthInLabel = labelWidth;
        imgHeightInLabel = int(scaledRate * imgHeight);
        imageRect.setRect(0, (labelHeight - imgHeightInLabel) / 2,
                          imgWidthInLabel, imgHeightInLabel);
    }

    tempImage = originalImage.scaled(imgWidthInLabel, imgHeightInLabel,
                                     Qt::KeepAspectRatio, Qt::SmoothTransformation);
    this->setPixmap(tempImage);

    if (cropperShape >= CropperShape::FIXED_RECT) {
        cropperRect.setWidth(int(cropperRect_.width() * scaledRate));
        cropperRect.setHeight(int(cropperRect_.height() * scaledRate));
    }
    resetCropperPos();
}


/*****************************************
 * set cropper's shape (and size)
 *****************************************/
void ImageCropperLabel::setRectCropper() {
    cropperShape = CropperShape::RECT;
    resetCropperPos();
}

void ImageCropperLabel::setSquareCropper() {
    cropperShape = CropperShape::SQUARE;
    resetCropperPos();
}

void ImageCropperLabel::setEllipseCropper() {
    cropperShape = CropperShape::ELLIPSE;
    resetCropperPos();
}

void ImageCropperLabel::setCircleCropper() {
    cropperShape = CropperShape::CIRCLE;
    resetCropperPos();
}

void ImageCropperLabel::setFixedRectCropper(QSize size) {
    cropperShape = CropperShape::FIXED_RECT;
    cropperRect_.setSize(size);
    resetCropperPos();
}

void ImageCropperLabel::setFixedEllipseCropper(QSize size) {
    cropperShape = CropperShape::FIXED_ELLIPSE;
    cropperRect_.setSize(size);
    resetCropperPos();
}

// not recommended
void ImageCropperLabel::setCropper(CropperShape shape, QSize size) {
    cropperShape = shape;
    cropperRect_.setSize(size);
    resetCropperPos();
}

/*****************************************************************************
 * Set cropper's fixed size
 *****************************************************************************/
void ImageCropperLabel::setCropperFixedSize(int fixedWidth, int fixedHeight) {
    cropperRect_.setSize(QSize(fixedWidth, fixedHeight));
    resetCropperPos();
}

void ImageCropperLabel::setCropperFixedWidth(int fixedWidth) {
    cropperRect_.setWidth(fixedWidth);
    resetCropperPos();
}

void ImageCropperLabel::setCropperFixedHeight(int fixedHeight) {
    cropperRect_.setHeight(fixedHeight);
    resetCropperPos();
}

/**********************************************
 * Move cropper to the center of the image
 * And resize to default
 **********************************************/
void ImageCropperLabel::resetCropperPos() {
    int labelWidth = this->width();
    int labelHeight = this->height();

    if (cropperShape == CropperShape::FIXED_RECT || cropperShape == CropperShape::FIXED_ELLIPSE) {
        cropperRect.setWidth(int(cropperRect_.width() * scaledRate));
        cropperRect.setHeight(int(cropperRect_.height() * scaledRate));
    }

    switch (cropperShape) {
    case CropperShape::UNDEFINED:
        break;
    case CropperShape::FIXED_RECT:
    case CropperShape::FIXED_ELLIPSE: {
        cropperRect.setRect((labelWidth - cropperRect.width()) / 2,
                            (labelHeight - cropperRect.height()) / 2,
                            cropperRect.width(), cropperRect.height());
        break;
    }
    case CropperShape::RECT:
    case CropperShape::SQUARE:
    case CropperShape::ELLIPSE:
    case CropperShape::CIRCLE: {
        int imgWidth = tempImage.width();
        int imgHeight = tempImage.height();
        int edge = int((imgWidth > imgHeight ? imgHeight : imgWidth) * 3 / 4.0);
        cropperRect.setRect((labelWidth - edge) / 2, (labelHeight - edge) / 2, edge, edge);
        break;
    }
    }
}

QPixmap ImageCropperLabel::getCroppedImage() {
    return getCroppedImage(this->outputShape);
}

QPixmap ImageCropperLabel::getCroppedImage(OutputShape shape) {
    int startX = int((cropperRect.left() - imageRect.left()) / scaledRate);
    int startY = int((cropperRect.top() - imageRect.top()) / scaledRate);
    int croppedWidth = int(cropperRect.width() / scaledRate);
    int croppedHeight = int(cropperRect.height() / scaledRate);

    QPixmap resultImage(croppedWidth, croppedHeight);
    resultImage = originalImage.copy(startX, startY, croppedWidth, croppedHeight);

           // Set ellipse mask (cut to ellipse shape)
    if (shape == OutputShape::ELLIPSE) {
        QSize size(croppedWidth, croppedHeight);
        QBitmap mask(size);
        QPainter painter(&mask);
        painter.setRenderHint(QPainter::Antialiasing);
        painter.setRenderHint(QPainter::SmoothPixmapTransform);
        painter.fillRect(0, 0, size.width(), size.height(), Qt::white);
        painter.setBrush(QColor(0, 0, 0));
        painter.drawRoundedRect(0, 0, size.width(), size.height(), 99, 99);
        resultImage.setMask(mask);
    }

    return resultImage;
}


void ImageCropperLabel::paintEvent(QPaintEvent *event) {
    // Draw original image
    QLabel::paintEvent(event);

           // Draw cropper and set some effects
    switch (cropperShape) {
    case CropperShape::UNDEFINED:
        break;
    case CropperShape::FIXED_RECT:
        drawRectOpacity();
        break;
    case CropperShape::FIXED_ELLIPSE:
        drawEllipseOpacity();
        break;
    case CropperShape::RECT:
        drawRectOpacity();
        drawSquareEdge(!ONLY_FOUR_CORNERS);
        break;
    case CropperShape::SQUARE:
        drawRectOpacity();
        drawSquareEdge(ONLY_FOUR_CORNERS);
        break;
    case CropperShape::ELLIPSE:
        drawEllipseOpacity();
        drawSquareEdge(!ONLY_FOUR_CORNERS);
        break;
    case CropperShape::CIRCLE:
        drawEllipseOpacity();
        drawSquareEdge(ONLY_FOUR_CORNERS);
        break;
    }

           // Draw cropper rect
    if (isShowRectBorder) {
        QPainter painter(this);
        painter.setPen(borderPen);
        painter.drawRect(cropperRect);
    }
}

void ImageCropperLabel::drawSquareEdge(bool onlyFourCorners) {
    if (!isShowDragSquare)
        return;

           // Four corners
    drawFillRect(cropperRect.topLeft(), dragSquareEdge, dragSquareColor);
    drawFillRect(cropperRect.topRight(), dragSquareEdge, dragSquareColor);
    drawFillRect(cropperRect.bottomLeft(), dragSquareEdge, dragSquareColor);
    drawFillRect(cropperRect.bottomRight(), dragSquareEdge, dragSquareColor);

           // Four edges
    if (!onlyFourCorners) {
        int centralX = cropperRect.left() + cropperRect.width() / 2;
        int centralY = cropperRect.top() + cropperRect.height() / 2;
        drawFillRect(QPoint(cropperRect.left(), centralY), dragSquareEdge, dragSquareColor);
        drawFillRect(QPoint(centralX, cropperRect.top()), dragSquareEdge, dragSquareColor);
        drawFillRect(QPoint(cropperRect.right(), centralY), dragSquareEdge, dragSquareColor);
        drawFillRect(QPoint(centralX, cropperRect.bottom()), dragSquareEdge, dragSquareColor);
    }
}

void ImageCropperLabel::drawFillRect(QPoint centralPoint, int edge, QColor color) {
    QRect rect(centralPoint.x() - edge / 2, centralPoint.y() - edge / 2, edge, edge);
    QPainter painter(this);
    painter.fillRect(rect, color);
}

// Opacity effect
void ImageCropperLabel::drawOpacity(const QPainterPath& path) {
    QPainter painterOpac(this);
    painterOpac.setOpacity(opacity);
    painterOpac.fillPath(path, QBrush(Qt::black));
}

void ImageCropperLabel::drawRectOpacity() {
    if (isShowOpacityEffect) {
        QPainterPath p1, p2, p;
        p1.addRect(imageRect);
        p2.addRect(cropperRect);
        p = p1.subtracted(p2);
        drawOpacity(p);
    }
}

void ImageCropperLabel::drawEllipseOpacity() {
    if (isShowOpacityEffect) {
        QPainterPath p1, p2, p;
        p1.addRect(imageRect);
        p2.addEllipse(cropperRect);
        p = p1.subtracted(p2);
        drawOpacity(p);
    }
}

bool ImageCropperLabel::isPosNearDragSquare(const QPoint& pt1, const QPoint& pt2) {
    return abs(pt1.x() - pt2.x()) * 2 <= dragSquareEdge
           && abs(pt1.y() - pt2.y()) * 2 <= dragSquareEdge;
}

int ImageCropperLabel::getPosInCropperRect(const QPoint &pt) {
    if (isPosNearDragSquare(pt, QPoint(cropperRect.right(), cropperRect.center().y())))
        return RECT_RIGHT;
    if (isPosNearDragSquare(pt, cropperRect.bottomRight()))
        return RECT_BOTTOM_RIGHT;
    if (isPosNearDragSquare(pt, QPoint(cropperRect.center().x(), cropperRect.bottom())))
        return RECT_BOTTOM;
    if (isPosNearDragSquare(pt, cropperRect.bottomLeft()))
        return RECT_BOTTOM_LEFT;
    if (isPosNearDragSquare(pt, QPoint(cropperRect.left(), cropperRect.center().y())))
        return RECT_LEFT;
    if (isPosNearDragSquare(pt, cropperRect.topLeft()))
        return RECT_TOP_LEFT;
    if (isPosNearDragSquare(pt, QPoint(cropperRect.center().x(), cropperRect.top())))
        return RECT_TOP;
    if (isPosNearDragSquare(pt, cropperRect.topRight()))
        return RECT_TOP_RIGHT;
    if (cropperRect.contains(pt, true))
        return RECT_INSIDE;
    return RECT_OUTSIZD;
}

/*************************************************
 *
 *  Change mouse cursor type
 *      Arrow, SizeHor, SizeVer, etc...
 *
 *************************************************/

void ImageCropperLabel::changeCursor() {
    switch (cursorPosInCropperRect) {
    case RECT_OUTSIZD:
        setCursor(Qt::ArrowCursor);
        break;
    case RECT_BOTTOM_RIGHT: {
        switch (cropperShape) {
        case CropperShape::SQUARE:
        case CropperShape::CIRCLE:
        case CropperShape::RECT:
        case CropperShape::ELLIPSE:
            setCursor(Qt::SizeFDiagCursor);
            break;
        default:
            break;
        }
        break;
    }
    case RECT_RIGHT: {
        switch (cropperShape) {
        case CropperShape::RECT:
        case CropperShape::ELLIPSE:
            setCursor(Qt::SizeHorCursor);
            break;
        default:
            break;
        }
        break;
    }
    case RECT_BOTTOM: {
        switch (cropperShape) {
        case CropperShape::RECT:
        case CropperShape::ELLIPSE:
            setCursor(Qt::SizeVerCursor);
            break;
        default:
            break;
        }
        break;
    }
    case RECT_BOTTOM_LEFT: {
        switch (cropperShape) {
        case CropperShape::RECT:
        case CropperShape::ELLIPSE:
        case CropperShape::SQUARE:
        case CropperShape::CIRCLE:
            setCursor(Qt::SizeBDiagCursor);
            break;
        default:
            break;
        }
        break;
    }
    case RECT_LEFT: {
        switch (cropperShape) {
        case CropperShape::RECT:
        case CropperShape::ELLIPSE:
            setCursor(Qt::SizeHorCursor);
            break;
        default:
            break;
        }
        break;
    }
    case RECT_TOP_LEFT: {
        switch (cropperShape) {
        case CropperShape::RECT:
        case CropperShape::ELLIPSE:
        case CropperShape::SQUARE:
        case CropperShape::CIRCLE:
            setCursor(Qt::SizeFDiagCursor);
            break;
        default:
            break;
        }
        break;
    }
    case RECT_TOP: {
        switch (cropperShape) {
        case CropperShape::RECT:
        case CropperShape::ELLIPSE:
            setCursor(Qt::SizeVerCursor);
            break;
        default:
            break;
        }
        break;
    }
    case RECT_TOP_RIGHT: {
        switch (cropperShape) {
        case CropperShape::SQUARE:
        case CropperShape::CIRCLE:
        case CropperShape::RECT:
        case CropperShape::ELLIPSE:
            setCursor(Qt::SizeBDiagCursor);
            break;
        default:
            break;
        }
        break;
    }
    case RECT_INSIDE: {
        setCursor(Qt::SizeAllCursor);
        break;
    }
    }
}

/*****************************************************
 *
 *  Mouse Events
 *
 *****************************************************/

void ImageCropperLabel::mousePressEvent(QMouseEvent *e) {
    currPos = lastPos = e->pos();
    isLButtonPressed = true;
}

void ImageCropperLabel::mouseMoveEvent(QMouseEvent *e) {
    currPos = e->pos();
    if (!isCursorPosCalculated) {
        cursorPosInCropperRect = getPosInCropperRect(currPos);
        changeCursor();
    }

    if (!isLButtonPressed)
        return;
    if (!imageRect.contains(currPos))
        return;

    isCursorPosCalculated = true;

    int xOffset = currPos.x() - lastPos.x();
    int yOffset = currPos.y() - lastPos.y();
    lastPos = currPos;

    int disX = 0;
    int disY = 0;

           // Move cropper
    switch (cursorPosInCropperRect) {
    case RECT_OUTSIZD:
        break;
    case RECT_BOTTOM_RIGHT: {
        disX = currPos.x() - cropperRect.left();
        disY = currPos.y() - cropperRect.top();
        switch (cropperShape) {
        case CropperShape::UNDEFINED:
        case CropperShape::FIXED_RECT:
        case CropperShape::FIXED_ELLIPSE:
            break;
        case CropperShape::SQUARE:
        case CropperShape::CIRCLE:
            setCursor(Qt::SizeFDiagCursor);
            if (disX >= cropperMinimumWidth && disY >= cropperMinimumHeight) {
                if (disX > disY && cropperRect.top() + disX <= imageRect.bottom()) {
                    cropperRect.setRight(currPos.x());
                    cropperRect.setBottom(cropperRect.top() + disX);
                    emit croppedImageChanged();
                }
                else if (disX <= disY && cropperRect.left() + disY <= imageRect.right()) {
                    cropperRect.setBottom(currPos.y());
                    cropperRect.setRight(cropperRect.left() + disY);
                    emit croppedImageChanged();
                }
            }
            break;
        case CropperShape::RECT:
        case CropperShape::ELLIPSE:
            setCursor(Qt::SizeFDiagCursor);
            if (disX >= cropperMinimumWidth) {
                cropperRect.setRight(currPos.x());
                emit croppedImageChanged();
            }
            if (disY >= cropperMinimumHeight) {
                cropperRect.setBottom(currPos.y());
                emit croppedImageChanged();
            }
            break;
        }
        break;
    }
    case RECT_RIGHT: {
        disX = currPos.x() - cropperRect.left();
        switch (cropperShape) {
        case CropperShape::UNDEFINED:
        case CropperShape::FIXED_RECT:
        case CropperShape::FIXED_ELLIPSE:
        case CropperShape::SQUARE:
        case CropperShape::CIRCLE:
            break;
        case CropperShape::RECT:
        case CropperShape::ELLIPSE:
            if (disX >= cropperMinimumWidth) {
                cropperRect.setRight(currPos.x());
                emit croppedImageChanged();
            }
            break;
        }
        break;
    }
    case RECT_BOTTOM: {
        disY = currPos.y() - cropperRect.top();
        switch (cropperShape) {
        case CropperShape::UNDEFINED:
        case CropperShape::FIXED_RECT:
        case CropperShape::FIXED_ELLIPSE:
        case CropperShape::SQUARE:
        case CropperShape::CIRCLE:
            break;
        case CropperShape::RECT:
        case CropperShape::ELLIPSE:
            if (disY >= cropperMinimumHeight) {
                cropperRect.setBottom(cropperRect.bottom() + yOffset);
                emit croppedImageChanged();
            }
            break;
        }
        break;
    }
    case RECT_BOTTOM_LEFT: {
        disX = cropperRect.right() - currPos.x();
        disY = currPos.y() - cropperRect.top();
        switch (cropperShape) {
        case CropperShape::UNDEFINED:
            break;
        case CropperShape::FIXED_RECT:
        case CropperShape::FIXED_ELLIPSE:
        case CropperShape::RECT:
        case CropperShape::ELLIPSE:
            if (disX >= cropperMinimumWidth) {
                cropperRect.setLeft(currPos.x());
                emit croppedImageChanged();
            }
            if (disY >= cropperMinimumHeight) {
                cropperRect.setBottom(currPos.y());
                emit croppedImageChanged();
            }
            break;
        case CropperShape::SQUARE:
        case CropperShape::CIRCLE:
            if (disX >= cropperMinimumWidth && disY >= cropperMinimumHeight) {
                if (disX > disY && cropperRect.top() + disX <= imageRect.bottom()) {
                    cropperRect.setLeft(currPos.x());
                    cropperRect.setBottom(cropperRect.top() + disX);
                    emit croppedImageChanged();
                }
                else if (disX <= disY && cropperRect.right() - disY >= imageRect.left()) {
                    cropperRect.setBottom(currPos.y());
                    cropperRect.setLeft(cropperRect.right() - disY);
                    emit croppedImageChanged();
                }
            }
            break;
        }
        break;
    }
    case RECT_LEFT: {
        disX = cropperRect.right() - currPos.x();
        switch (cropperShape) {
        case CropperShape::UNDEFINED:
        case CropperShape::FIXED_RECT:
        case CropperShape::FIXED_ELLIPSE:
        case CropperShape::SQUARE:
        case CropperShape::CIRCLE:
            break;
        case CropperShape::RECT:
        case CropperShape::ELLIPSE:
            if (disX >= cropperMinimumHeight) {
                cropperRect.setLeft(cropperRect.left() + xOffset);
                emit croppedImageChanged();
            }
            break;
        }
        break;
    }
    case RECT_TOP_LEFT: {
        disX = cropperRect.right() - currPos.x();
        disY = cropperRect.bottom() - currPos.y();
        switch (cropperShape) {
        case CropperShape::UNDEFINED:
        case CropperShape::FIXED_RECT:
        case CropperShape::FIXED_ELLIPSE:
            break;
        case CropperShape::RECT:
        case CropperShape::ELLIPSE:
            if (disX >= cropperMinimumWidth) {
                cropperRect.setLeft(currPos.x());
                emit croppedImageChanged();
            }
            if (disY >= cropperMinimumHeight) {
                cropperRect.setTop(currPos.y());
                emit croppedImageChanged();
            }
            break;
        case CropperShape::SQUARE:
        case CropperShape::CIRCLE:
            if (disX >= cropperMinimumWidth && disY >= cropperMinimumHeight) {
                if (disX > disY && cropperRect.bottom() - disX >= imageRect.top()) {
                    cropperRect.setLeft(currPos.x());
                    cropperRect.setTop(cropperRect.bottom() - disX);
                    emit croppedImageChanged();
                }
                else if (disX <= disY && cropperRect.right() - disY >= imageRect.left()) {
                    cropperRect.setTop(currPos.y());
                    cropperRect.setLeft(cropperRect.right() - disY);
                    emit croppedImageChanged();
                }
            }
            break;
        }
        break;
    }
    case RECT_TOP: {
        disY = cropperRect.bottom() - currPos.y();
        switch (cropperShape) {
        case CropperShape::UNDEFINED:
        case CropperShape::FIXED_RECT:
        case CropperShape::FIXED_ELLIPSE:
        case CropperShape::SQUARE:
        case CropperShape::CIRCLE:
            break;
        case CropperShape::RECT:
        case CropperShape::ELLIPSE:
            if (disY >= cropperMinimumHeight) {
                cropperRect.setTop(cropperRect.top() + yOffset);
                emit croppedImageChanged();
            }
            break;
        }
        break;
    }
    case RECT_TOP_RIGHT: {
        disX = currPos.x() - cropperRect.left();
        disY = cropperRect.bottom() - currPos.y();
        switch (cropperShape) {
        case CropperShape::UNDEFINED:
        case CropperShape::FIXED_RECT:
        case CropperShape::FIXED_ELLIPSE:
            break;
        case CropperShape::RECT:
        case CropperShape::ELLIPSE:
            if (disX >= cropperMinimumWidth) {
                cropperRect.setRight(currPos.x());
                emit croppedImageChanged();
            }
            if (disY >= cropperMinimumHeight) {
                cropperRect.setTop(currPos.y());
                emit croppedImageChanged();
            }
            break;
        case CropperShape::SQUARE:
        case CropperShape::CIRCLE:
            if (disX >= cropperMinimumWidth && disY >= cropperMinimumHeight) {
                if (disX < disY && cropperRect.left() + disY <= imageRect.right()) {
                    cropperRect.setTop(currPos.y());
                    cropperRect.setRight(cropperRect.left() + disY);
                    emit croppedImageChanged();
                }
                else if (disX >= disY && cropperRect.bottom() - disX >= imageRect.top()) {
                    cropperRect.setRight(currPos.x());
                    cropperRect.setTop(cropperRect.bottom() - disX);
                    emit croppedImageChanged();
                }
            }
            break;
        }
        break;
    }
    case RECT_INSIDE: {
        // Make sure the cropperRect is entirely inside the imageRecct
        if (xOffset > 0) {
            if (cropperRect.right() + xOffset > imageRect.right())
                xOffset = 0;
        }
        else if (xOffset < 0) {
            if (cropperRect.left() + xOffset < imageRect.left())
                xOffset = 0;
        }
        if (yOffset > 0) {
            if (cropperRect.bottom() + yOffset > imageRect.bottom())
                yOffset = 0;
        }
        else if (yOffset < 0) {
            if (cropperRect.top() + yOffset < imageRect.top())
                yOffset = 0;
        }
        cropperRect.moveTo(cropperRect.left() + xOffset, cropperRect.top() + yOffset);
        emit croppedImageChanged();
    }
    break;
    }

    repaint();
}

void ImageCropperLabel::mouseReleaseEvent(QMouseEvent *) {
    isLButtonPressed = false;
    isCursorPosCalculated = false;
    setCursor(Qt::ArrowCursor);
}

posted @ 2025-12-24 23:18  大胖熊哈  阅读(3)  评论(0)    收藏  举报