initial commit
This commit is contained in:
209
client/gui/dialogs/ImageCropDialog.cpp
Normal file
209
client/gui/dialogs/ImageCropDialog.cpp
Normal file
@@ -0,0 +1,209 @@
|
||||
#include "dialogs/ImageCropDialog.h"
|
||||
|
||||
#include <QBoxLayout>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QLabel>
|
||||
#include <QMouseEvent>
|
||||
#include <QPainter>
|
||||
#include <QPushButton>
|
||||
#include <QtMath>
|
||||
|
||||
class ImageCropDialog::CropView final : public QWidget {
|
||||
public:
|
||||
explicit CropView(QWidget* parent = nullptr)
|
||||
: QWidget(parent) {
|
||||
setMouseTracking(true);
|
||||
setMinimumSize(480, 320);
|
||||
}
|
||||
|
||||
void setImage(const QImage& img) {
|
||||
m_image = img;
|
||||
m_selection = {};
|
||||
updateGeometry();
|
||||
update();
|
||||
}
|
||||
|
||||
bool hasSelection() const { return !m_selection.isNull() && m_selection.width() > 0 && m_selection.height() > 0; }
|
||||
|
||||
QRect selectionInImagePixels() const {
|
||||
if (m_image.isNull() || !hasSelection()) {
|
||||
return {};
|
||||
}
|
||||
const auto map = viewToImageTransform();
|
||||
// selection 是 view 坐标;映射到 image 像素坐标
|
||||
const QRectF selF = QRectF(m_selection).normalized();
|
||||
bool invertible = false;
|
||||
const QTransform inv = map.inverted(&invertible);
|
||||
if (!invertible) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const QPointF topLeftImg = inv.map(selF.topLeft());
|
||||
const QPointF bottomRightImg = inv.map(selF.bottomRight());
|
||||
|
||||
// 使用 floor/ceil,避免因为取整导致宽高变 0
|
||||
const int left = qFloor(std::min(topLeftImg.x(), bottomRightImg.x()));
|
||||
const int top = qFloor(std::min(topLeftImg.y(), bottomRightImg.y()));
|
||||
const int right = qCeil(std::max(topLeftImg.x(), bottomRightImg.x()));
|
||||
const int bottom = qCeil(std::max(topLeftImg.y(), bottomRightImg.y()));
|
||||
|
||||
QRect r(QPoint(left, top), QPoint(right, bottom));
|
||||
r = r.normalized().intersected(QRect(0, 0, m_image.width(), m_image.height()));
|
||||
return r;
|
||||
}
|
||||
|
||||
void resetSelection() {
|
||||
m_selection = {};
|
||||
update();
|
||||
}
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent*) override {
|
||||
QPainter p(this);
|
||||
p.fillRect(rect(), palette().window());
|
||||
|
||||
if (m_image.isNull()) {
|
||||
p.setPen(palette().text().color());
|
||||
p.drawText(rect(), Qt::AlignCenter, QStringLiteral("无法加载图片"));
|
||||
return;
|
||||
}
|
||||
|
||||
const auto map = viewToImageTransform();
|
||||
p.setRenderHint(QPainter::SmoothPixmapTransform, true);
|
||||
p.setTransform(map);
|
||||
p.drawImage(QPoint(0, 0), m_image);
|
||||
p.resetTransform();
|
||||
|
||||
if (hasSelection()) {
|
||||
// 避免 CompositionMode_Clear 在某些平台/样式下表现异常:
|
||||
// 用“围绕选区画四块遮罩”的方式实现高亮裁剪区域。
|
||||
const QRect sel = m_selection.normalized().intersected(rect());
|
||||
const QColor shade(0, 0, 0, 120);
|
||||
|
||||
// 上
|
||||
p.fillRect(QRect(0, 0, width(), sel.top()), shade);
|
||||
// 下
|
||||
p.fillRect(QRect(0, sel.bottom(), width(), height() - sel.bottom()), shade);
|
||||
// 左
|
||||
p.fillRect(QRect(0, sel.top(), sel.left(), sel.height()), shade);
|
||||
// 右
|
||||
p.fillRect(QRect(sel.right(), sel.top(), width() - sel.right(), sel.height()), shade);
|
||||
|
||||
p.setPen(QPen(QColor(255, 255, 255, 220), 2));
|
||||
p.drawRect(sel);
|
||||
}
|
||||
}
|
||||
|
||||
void mousePressEvent(QMouseEvent* e) override {
|
||||
if (m_image.isNull() || e->button() != Qt::LeftButton) {
|
||||
return;
|
||||
}
|
||||
m_dragging = true;
|
||||
m_anchor = e->position().toPoint();
|
||||
m_selection = QRect(m_anchor, m_anchor);
|
||||
update();
|
||||
}
|
||||
|
||||
void mouseMoveEvent(QMouseEvent* e) override {
|
||||
if (!m_dragging) {
|
||||
return;
|
||||
}
|
||||
const QPoint cur = e->position().toPoint();
|
||||
m_selection = QRect(m_anchor, cur).normalized();
|
||||
update();
|
||||
}
|
||||
|
||||
void mouseReleaseEvent(QMouseEvent* e) override {
|
||||
if (e->button() != Qt::LeftButton) {
|
||||
return;
|
||||
}
|
||||
m_dragging = false;
|
||||
update();
|
||||
}
|
||||
|
||||
private:
|
||||
QTransform viewToImageTransform() const {
|
||||
// 让图片按比例 fit 到 view 中居中显示
|
||||
const QSizeF viewSize = size();
|
||||
const QSizeF imgSize = m_image.size();
|
||||
const qreal sx = viewSize.width() / imgSize.width();
|
||||
const qreal sy = viewSize.height() / imgSize.height();
|
||||
const qreal s = std::min(sx, sy);
|
||||
|
||||
const qreal drawW = imgSize.width() * s;
|
||||
const qreal drawH = imgSize.height() * s;
|
||||
const qreal offsetX = (viewSize.width() - drawW) / 2.0;
|
||||
const qreal offsetY = (viewSize.height() - drawH) / 2.0;
|
||||
|
||||
QTransform t;
|
||||
t.translate(offsetX, offsetY);
|
||||
t.scale(s, s);
|
||||
return t;
|
||||
}
|
||||
|
||||
private:
|
||||
QImage m_image;
|
||||
bool m_dragging = false;
|
||||
QPoint m_anchor;
|
||||
QRect m_selection;
|
||||
};
|
||||
|
||||
ImageCropDialog::ImageCropDialog(const QString& imagePath, QWidget* parent)
|
||||
: QDialog(parent),
|
||||
m_imagePath(imagePath) {
|
||||
setWindowTitle(QStringLiteral("裁剪图片"));
|
||||
setModal(true);
|
||||
resize(900, 600);
|
||||
loadImageOrClose();
|
||||
rebuildUi();
|
||||
}
|
||||
|
||||
void ImageCropDialog::loadImageOrClose() {
|
||||
m_image = QImage(m_imagePath);
|
||||
if (m_image.isNull()) {
|
||||
reject();
|
||||
}
|
||||
}
|
||||
|
||||
void ImageCropDialog::rebuildUi() {
|
||||
auto* root = new QVBoxLayout(this);
|
||||
|
||||
auto* hint = new QLabel(QStringLiteral("拖拽选择裁剪区域(不选则使用整张图)。"), this);
|
||||
root->addWidget(hint);
|
||||
|
||||
m_view = new CropView(this);
|
||||
m_view->setImage(m_image);
|
||||
root->addWidget(m_view, 1);
|
||||
|
||||
auto* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
|
||||
m_okButton = buttons->button(QDialogButtonBox::Ok);
|
||||
auto* resetBtn = new QPushButton(QStringLiteral("重置选择"), this);
|
||||
buttons->addButton(resetBtn, QDialogButtonBox::ActionRole);
|
||||
|
||||
connect(resetBtn, &QPushButton::clicked, this, &ImageCropDialog::onReset);
|
||||
connect(buttons, &QDialogButtonBox::accepted, this, &ImageCropDialog::onOk);
|
||||
connect(buttons, &QDialogButtonBox::rejected, this, &ImageCropDialog::reject);
|
||||
root->addWidget(buttons);
|
||||
}
|
||||
|
||||
bool ImageCropDialog::hasValidSelection() const {
|
||||
return m_view && m_view->hasSelection();
|
||||
}
|
||||
|
||||
QRect ImageCropDialog::selectedRectInImagePixels() const {
|
||||
if (!m_view) {
|
||||
return {};
|
||||
}
|
||||
return m_view->selectionInImagePixels();
|
||||
}
|
||||
|
||||
void ImageCropDialog::onReset() {
|
||||
if (m_view) {
|
||||
m_view->resetSelection();
|
||||
}
|
||||
}
|
||||
|
||||
void ImageCropDialog::onOk() {
|
||||
accept();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user