update
This commit is contained in:
@@ -1,64 +1,122 @@
|
||||
#include "dialogs/ImageCropDialog.h"
|
||||
|
||||
#include "core/image/ImageDecodeConfig.h"
|
||||
#include "core/image/ImageFileLoader.h"
|
||||
|
||||
#include <QBoxLayout>
|
||||
#include <QCursor>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QLabel>
|
||||
#include <QMouseEvent>
|
||||
#include <QPainter>
|
||||
#include <QPushButton>
|
||||
#include <QImageReader>
|
||||
#include <QResizeEvent>
|
||||
#include <QWheelEvent>
|
||||
#include <QtMath>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
# define CROP_LOCAL_POS_M(E) (E)->position()
|
||||
# define CROP_LOCAL_POS_W(E) (E)->position()
|
||||
#else
|
||||
# define CROP_LOCAL_POS_M(E) (E)->localPos()
|
||||
# define CROP_LOCAL_POS_W(E) (E)->posF()
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr int kMinCropSidePx = 2;
|
||||
/// 相对「适应窗口」倍率的乘子;范围极大以便实质任意缩放(仍避免数值溢出)
|
||||
constexpr qreal kZoomMulMin = 1e-12;
|
||||
constexpr qreal kZoomMulMax = 1e12;
|
||||
constexpr qreal kHandleViewPx = 7.0;
|
||||
|
||||
enum class HitPart : quint8 {
|
||||
None,
|
||||
Inside,
|
||||
N,
|
||||
Ne,
|
||||
E,
|
||||
Se,
|
||||
S,
|
||||
Sw,
|
||||
W,
|
||||
Nw,
|
||||
/// 平移视图(选区外左键拖动)
|
||||
PanView
|
||||
};
|
||||
|
||||
QRect clampRectToBounds(QRect r, const QRect& bounds) {
|
||||
r = r.normalized();
|
||||
if (r.width() < kMinCropSidePx || r.height() < kMinCropSidePx) {
|
||||
return {};
|
||||
}
|
||||
if (r.left() < bounds.left()) {
|
||||
r.moveLeft(bounds.left());
|
||||
}
|
||||
if (r.top() < bounds.top()) {
|
||||
r.moveTop(bounds.top());
|
||||
}
|
||||
if (r.right() > bounds.right()) {
|
||||
r.moveRight(bounds.right());
|
||||
}
|
||||
if (r.bottom() > bounds.bottom()) {
|
||||
r.moveBottom(bounds.bottom());
|
||||
}
|
||||
if (r.width() < kMinCropSidePx || r.height() < kMinCropSidePx) {
|
||||
return {};
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class ImageCropDialog::CropView final : public QWidget {
|
||||
public:
|
||||
explicit CropView(QWidget* parent = nullptr)
|
||||
: QWidget(parent) {
|
||||
setMouseTracking(true);
|
||||
setFocusPolicy(Qt::WheelFocus);
|
||||
setMinimumSize(480, 320);
|
||||
}
|
||||
|
||||
void setImage(const QImage& img) {
|
||||
m_image = img;
|
||||
m_selection = {};
|
||||
m_zoom = 1.0;
|
||||
m_pan = QPointF();
|
||||
m_selImage = m_image.isNull() ? QRect() : QRect(QPoint(0, 0), m_image.size());
|
||||
m_dragPart = HitPart::None;
|
||||
updateGeometry();
|
||||
update();
|
||||
}
|
||||
|
||||
bool hasSelection() const { return !m_selection.isNull() && m_selection.width() > 0 && m_selection.height() > 0; }
|
||||
bool hasSelection() const {
|
||||
return !m_image.isNull() && m_selImage.width() >= kMinCropSidePx && m_selImage.height() >= kMinCropSidePx;
|
||||
}
|
||||
|
||||
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;
|
||||
return m_selImage.normalized().intersected(QRect(0, 0, m_image.width(), m_image.height()));
|
||||
}
|
||||
|
||||
void resetSelection() {
|
||||
m_selection = {};
|
||||
if (!m_image.isNull()) {
|
||||
m_selImage = QRect(QPoint(0, 0), m_image.size());
|
||||
} else {
|
||||
m_selImage = {};
|
||||
}
|
||||
update();
|
||||
}
|
||||
|
||||
protected:
|
||||
void resizeEvent(QResizeEvent* e) override {
|
||||
QWidget::resizeEvent(e);
|
||||
update();
|
||||
}
|
||||
|
||||
void paintEvent(QPaintEvent*) override {
|
||||
QPainter p(this);
|
||||
p.fillRect(rect(), palette().window());
|
||||
@@ -69,84 +127,362 @@ protected:
|
||||
return;
|
||||
}
|
||||
|
||||
const auto map = viewToImageTransform();
|
||||
qreal eff = 0;
|
||||
QPointF tl;
|
||||
computeLayout(eff, tl);
|
||||
|
||||
const QTransform imgToView = imageToViewTransform(eff, tl);
|
||||
p.setRenderHint(QPainter::SmoothPixmapTransform, true);
|
||||
p.setTransform(map);
|
||||
p.setTransform(imgToView);
|
||||
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);
|
||||
const QRectF selView = imgToView.mapRect(QRectF(m_selImage));
|
||||
const QRect sel = selView.toAlignedRect().intersected(rect());
|
||||
|
||||
// 上
|
||||
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);
|
||||
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);
|
||||
p.setPen(QPen(QColor(255, 255, 255, 230), 2));
|
||||
p.setBrush(Qt::NoBrush);
|
||||
p.drawRect(sel);
|
||||
|
||||
const qreal hsz = kHandleViewPx;
|
||||
const QPointF c[4] = {
|
||||
selView.topLeft(),
|
||||
selView.topRight(),
|
||||
selView.bottomRight(),
|
||||
selView.bottomLeft(),
|
||||
};
|
||||
p.setPen(QPen(QColor(255, 255, 255, 240), 1));
|
||||
p.setBrush(QColor(255, 255, 255, 220));
|
||||
for (const QPointF& pt : c) {
|
||||
p.drawRect(QRectF(pt.x() - hsz * 0.5, pt.y() - hsz * 0.5, hsz, hsz));
|
||||
}
|
||||
}
|
||||
|
||||
void wheelEvent(QWheelEvent* e) override {
|
||||
if (m_image.isNull()) {
|
||||
e->ignore();
|
||||
return;
|
||||
}
|
||||
qreal effOld = 0;
|
||||
QPointF tlOld;
|
||||
computeLayout(effOld, tlOld);
|
||||
|
||||
const QPointF cursor = CROP_LOCAL_POS_W(e);
|
||||
const QPointF imgPt((cursor.x() - tlOld.x()) / effOld, (cursor.y() - tlOld.y()) / effOld);
|
||||
|
||||
const qreal steps = e->angleDelta().y() / 120.0;
|
||||
const qreal factor = std::pow(1.1, steps);
|
||||
const qreal newZoom = std::clamp(m_zoom * factor, kZoomMulMin, kZoomMulMax);
|
||||
if (qFuzzyCompare(newZoom, m_zoom)) {
|
||||
e->accept();
|
||||
return;
|
||||
}
|
||||
|
||||
m_zoom = newZoom;
|
||||
|
||||
qreal effNew = 0;
|
||||
QPointF tlIgnored;
|
||||
computeLayout(effNew, tlIgnored);
|
||||
|
||||
const QPointF tlNew(cursor.x() - imgPt.x() * effNew, cursor.y() - imgPt.y() * effNew);
|
||||
const qreal iw = m_image.width();
|
||||
const qreal ih = m_image.height();
|
||||
const qreal vw = width();
|
||||
const qreal vh = height();
|
||||
const QPointF centerOff((vw - iw * effNew) / 2.0, (vh - ih * effNew) / 2.0);
|
||||
m_pan = tlNew - centerOff;
|
||||
|
||||
e->accept();
|
||||
update();
|
||||
}
|
||||
|
||||
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();
|
||||
qreal eff = 0;
|
||||
QPointF tl;
|
||||
computeLayout(eff, tl);
|
||||
const QPointF viewPos = CROP_LOCAL_POS_M(e);
|
||||
const QPointF img = viewToImage(viewPos, eff, tl);
|
||||
|
||||
m_dragPart = hitTest(img, eff);
|
||||
if (m_dragPart == HitPart::None) {
|
||||
m_dragPart = HitPart::PanView;
|
||||
}
|
||||
if (m_dragPart == HitPart::PanView) {
|
||||
m_pressView = viewPos;
|
||||
m_panAtPress = m_pan;
|
||||
setCursor(Qt::ClosedHandCursor);
|
||||
return;
|
||||
}
|
||||
m_pressImg = img;
|
||||
m_startSel = m_selImage;
|
||||
updateCursorForHover(viewPos, eff, tl);
|
||||
}
|
||||
|
||||
void mouseMoveEvent(QMouseEvent* e) override {
|
||||
if (!m_dragging) {
|
||||
if (m_image.isNull()) {
|
||||
return;
|
||||
}
|
||||
const QPoint cur = e->position().toPoint();
|
||||
m_selection = QRect(m_anchor, cur).normalized();
|
||||
update();
|
||||
qreal eff = 0;
|
||||
QPointF tl;
|
||||
computeLayout(eff, tl);
|
||||
const QPointF viewPos = CROP_LOCAL_POS_M(e);
|
||||
|
||||
if ((e->buttons() & Qt::LeftButton) && m_dragPart == HitPart::PanView) {
|
||||
m_pan = m_panAtPress + (viewPos - m_pressView);
|
||||
update();
|
||||
return;
|
||||
}
|
||||
|
||||
if ((e->buttons() & Qt::LeftButton) && m_dragPart != HitPart::None && m_dragPart != HitPart::PanView) {
|
||||
const QPointF img = viewToImage(viewPos, eff, tl);
|
||||
applyDrag(m_dragPart, m_pressImg, img, m_startSel);
|
||||
update();
|
||||
return;
|
||||
}
|
||||
|
||||
updateCursorForHover(viewPos, eff, tl);
|
||||
}
|
||||
|
||||
void mouseReleaseEvent(QMouseEvent* e) override {
|
||||
if (e->button() != Qt::LeftButton) {
|
||||
return;
|
||||
if (e->button() == Qt::LeftButton) {
|
||||
m_dragPart = HitPart::None;
|
||||
unsetCursor();
|
||||
}
|
||||
m_dragging = false;
|
||||
update();
|
||||
QWidget::mouseReleaseEvent(e);
|
||||
}
|
||||
|
||||
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;
|
||||
void computeLayout(qreal& outEffScale, QPointF& outTopLeft) const {
|
||||
const qreal vw = width();
|
||||
const qreal vh = height();
|
||||
const qreal iw = m_image.width();
|
||||
const qreal ih = m_image.height();
|
||||
if (iw <= 0 || ih <= 0 || vw <= 1 || vh <= 1) {
|
||||
outEffScale = 1.0;
|
||||
outTopLeft = QPointF();
|
||||
return;
|
||||
}
|
||||
const qreal fit = std::min(vw / iw, vh / ih);
|
||||
const qreal eff = fit * m_zoom;
|
||||
outEffScale = eff;
|
||||
outTopLeft = m_pan + QPointF((vw - iw * eff) / 2.0, (vh - ih * eff) / 2.0);
|
||||
}
|
||||
|
||||
QTransform imageToViewTransform(qreal eff, const QPointF& tl) const {
|
||||
QTransform t;
|
||||
t.translate(offsetX, offsetY);
|
||||
t.scale(s, s);
|
||||
t.translate(tl.x(), tl.y());
|
||||
t.scale(eff, eff);
|
||||
return t;
|
||||
}
|
||||
|
||||
QPointF viewToImage(const QPointF& viewPt, qreal eff, const QPointF& tl) const {
|
||||
return QPointF((viewPt.x() - tl.x()) / eff, (viewPt.y() - tl.y()) / eff);
|
||||
}
|
||||
|
||||
qreal marginImage(qreal eff) const {
|
||||
return std::max<qreal>(kMinCropSidePx, kHandleViewPx / std::max(eff, 1e-6));
|
||||
}
|
||||
|
||||
HitPart hitTest(const QPointF& img, qreal eff) const {
|
||||
const qreal iw = m_image.width();
|
||||
const qreal ih = m_image.height();
|
||||
const QRectF boundsF(0, 0, iw, ih);
|
||||
if (!boundsF.contains(img)) {
|
||||
return HitPart::PanView;
|
||||
}
|
||||
const qreal m = marginImage(eff);
|
||||
const QRect r = m_selImage.normalized();
|
||||
|
||||
const bool onN = std::abs(img.y() - r.top()) <= m && img.x() >= r.left() - m && img.x() <= r.right() + m;
|
||||
const bool onS = std::abs(img.y() - r.bottom()) <= m && img.x() >= r.left() - m && img.x() <= r.right() + m;
|
||||
const bool onW = std::abs(img.x() - r.left()) <= m && img.y() >= r.top() - m && img.y() <= r.bottom() + m;
|
||||
const bool onE = std::abs(img.x() - r.right()) <= m && img.y() >= r.top() - m && img.y() <= r.bottom() + m;
|
||||
|
||||
const bool cnw = std::abs(img.x() - r.left()) <= m && std::abs(img.y() - r.top()) <= m;
|
||||
const bool cne = std::abs(img.x() - r.right()) <= m && std::abs(img.y() - r.top()) <= m;
|
||||
const bool cse = std::abs(img.x() - r.right()) <= m && std::abs(img.y() - r.bottom()) <= m;
|
||||
const bool csw = std::abs(img.x() - r.left()) <= m && std::abs(img.y() - r.bottom()) <= m;
|
||||
|
||||
if (cnw) {
|
||||
return HitPart::Nw;
|
||||
}
|
||||
if (cne) {
|
||||
return HitPart::Ne;
|
||||
}
|
||||
if (cse) {
|
||||
return HitPart::Se;
|
||||
}
|
||||
if (csw) {
|
||||
return HitPart::Sw;
|
||||
}
|
||||
if (onN && !onW && !onE) {
|
||||
return HitPart::N;
|
||||
}
|
||||
if (onS && !onW && !onE) {
|
||||
return HitPart::S;
|
||||
}
|
||||
if (onW && !onN && !onS) {
|
||||
return HitPart::W;
|
||||
}
|
||||
if (onE && !onN && !onS) {
|
||||
return HitPart::E;
|
||||
}
|
||||
const QRect fullImage(0, 0, m_image.width(), m_image.height());
|
||||
if (r.contains(img.toPoint())) {
|
||||
// 选区为整图时没有「可平移的选区」,框内左键用于平移视图
|
||||
if (r.normalized() == fullImage.normalized()) {
|
||||
return HitPart::PanView;
|
||||
}
|
||||
return HitPart::Inside;
|
||||
}
|
||||
return HitPart::PanView;
|
||||
}
|
||||
|
||||
void updateCursorForHover(const QPointF& viewPt, qreal eff, const QPointF& tl) {
|
||||
if (m_dragPart != HitPart::None) {
|
||||
return;
|
||||
}
|
||||
const QPointF img = viewToImage(viewPt, eff, tl);
|
||||
const HitPart h = hitTest(img, eff);
|
||||
switch (h) {
|
||||
case HitPart::PanView:
|
||||
setCursor(Qt::OpenHandCursor);
|
||||
break;
|
||||
case HitPart::Inside:
|
||||
setCursor(Qt::SizeAllCursor);
|
||||
break;
|
||||
case HitPart::N:
|
||||
setCursor(Qt::SizeVerCursor);
|
||||
break;
|
||||
case HitPart::S:
|
||||
setCursor(Qt::SizeVerCursor);
|
||||
break;
|
||||
case HitPart::W:
|
||||
setCursor(Qt::SizeHorCursor);
|
||||
break;
|
||||
case HitPart::E:
|
||||
setCursor(Qt::SizeHorCursor);
|
||||
break;
|
||||
case HitPart::Nw:
|
||||
case HitPart::Se:
|
||||
setCursor(Qt::SizeFDiagCursor);
|
||||
break;
|
||||
case HitPart::Ne:
|
||||
case HitPart::Sw:
|
||||
setCursor(Qt::SizeBDiagCursor);
|
||||
break;
|
||||
default:
|
||||
unsetCursor();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void applyDrag(HitPart part, const QPointF& pressImg, const QPointF& curImg, const QRect& start) {
|
||||
const QRect bounds(0, 0, m_image.width(), m_image.height());
|
||||
QRect r = start.normalized();
|
||||
const int L = r.left();
|
||||
const int T = r.top();
|
||||
const int R = r.right();
|
||||
const int B = r.bottom();
|
||||
|
||||
auto clampX = [&](int x) { return std::clamp(x, bounds.left(), bounds.right()); };
|
||||
auto clampY = [&](int y) { return std::clamp(y, bounds.top(), bounds.bottom()); };
|
||||
|
||||
switch (part) {
|
||||
case HitPart::Inside: {
|
||||
const QPoint delta = (curImg - pressImg).toPoint();
|
||||
r.translate(delta);
|
||||
r = clampRectToBounds(r, bounds);
|
||||
break;
|
||||
}
|
||||
case HitPart::N: {
|
||||
int nt = clampY(int(std::round(curImg.y())));
|
||||
nt = std::min(nt, B - kMinCropSidePx);
|
||||
r = QRect(L, nt, R - L + 1, B - nt + 1);
|
||||
break;
|
||||
}
|
||||
case HitPart::S: {
|
||||
int nb = clampY(int(std::round(curImg.y())));
|
||||
nb = std::max(nb, T + kMinCropSidePx);
|
||||
r = QRect(L, T, R - L + 1, nb - T + 1);
|
||||
break;
|
||||
}
|
||||
case HitPart::W: {
|
||||
int nl = clampX(int(std::round(curImg.x())));
|
||||
nl = std::min(nl, R - kMinCropSidePx);
|
||||
r = QRect(nl, T, R - nl + 1, B - T + 1);
|
||||
break;
|
||||
}
|
||||
case HitPart::E: {
|
||||
int nr = clampX(int(std::round(curImg.x())));
|
||||
nr = std::max(nr, L + kMinCropSidePx);
|
||||
r = QRect(L, T, nr - L + 1, B - T + 1);
|
||||
break;
|
||||
}
|
||||
case HitPart::Nw: {
|
||||
int nl = clampX(int(std::round(curImg.x())));
|
||||
int nt = clampY(int(std::round(curImg.y())));
|
||||
nl = std::min(nl, R - kMinCropSidePx);
|
||||
nt = std::min(nt, B - kMinCropSidePx);
|
||||
r = QRect(QPoint(nl, nt), QPoint(R, B)).normalized();
|
||||
break;
|
||||
}
|
||||
case HitPart::Ne: {
|
||||
int nr = clampX(int(std::round(curImg.x())));
|
||||
int nt = clampY(int(std::round(curImg.y())));
|
||||
nr = std::max(nr, L + kMinCropSidePx);
|
||||
nt = std::min(nt, B - kMinCropSidePx);
|
||||
r = QRect(QPoint(L, nt), QPoint(nr, B)).normalized();
|
||||
break;
|
||||
}
|
||||
case HitPart::Se: {
|
||||
int nr = clampX(int(std::round(curImg.x())));
|
||||
int nb = clampY(int(std::round(curImg.y())));
|
||||
nr = std::max(nr, L + kMinCropSidePx);
|
||||
nb = std::max(nb, T + kMinCropSidePx);
|
||||
r = QRect(QPoint(L, T), QPoint(nr, nb)).normalized();
|
||||
break;
|
||||
}
|
||||
case HitPart::Sw: {
|
||||
int nl = clampX(int(std::round(curImg.x())));
|
||||
int nb = clampY(int(std::round(curImg.y())));
|
||||
nl = std::min(nl, R - kMinCropSidePx);
|
||||
nb = std::max(nb, T + kMinCropSidePx);
|
||||
r = QRect(QPoint(nl, T), QPoint(R, nb)).normalized();
|
||||
break;
|
||||
}
|
||||
case HitPart::PanView:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
m_selImage = clampRectToBounds(r, bounds);
|
||||
if (m_selImage.isEmpty()) {
|
||||
m_selImage = start;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
QImage m_image;
|
||||
bool m_dragging = false;
|
||||
QPoint m_anchor;
|
||||
QRect m_selection;
|
||||
QRect m_selImage;
|
||||
double m_zoom = 1.0;
|
||||
QPointF m_pan;
|
||||
|
||||
HitPart m_dragPart = HitPart::None;
|
||||
QPointF m_pressImg;
|
||||
QRect m_startSel;
|
||||
QPointF m_pressView;
|
||||
QPointF m_panAtPress;
|
||||
};
|
||||
|
||||
ImageCropDialog::ImageCropDialog(const QString& imagePath, QWidget* parent)
|
||||
@@ -160,34 +496,30 @@ ImageCropDialog::ImageCropDialog(const QString& imagePath, QWidget* parent)
|
||||
}
|
||||
|
||||
void ImageCropDialog::loadImageOrClose() {
|
||||
// Qt 默认的 image allocation limit 较小(常见为 256MB),超大分辨率图会被拒绝。
|
||||
// 这里用 QImageReader 并提高 limit;同时对极端大图按像素数上限自动缩放,避免 OOM。
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
|
||||
QImageReader::setAllocationLimit(1024); // MB
|
||||
#endif
|
||||
QImageReader reader(m_imagePath);
|
||||
reader.setAutoTransform(true);
|
||||
const QSize sz = reader.size();
|
||||
if (sz.isValid()) {
|
||||
constexpr qint64 kMaxPixels = 120LL * 1000LL * 1000LL; // 120MP
|
||||
const qint64 pixels = qint64(sz.width()) * qint64(sz.height());
|
||||
if (pixels > kMaxPixels) {
|
||||
const double s = std::sqrt(double(kMaxPixels) / std::max<double>(1.0, double(pixels)));
|
||||
const int nw = std::max(1, int(std::lround(sz.width() * s)));
|
||||
const int nh = std::max(1, int(std::lround(sz.height() * s)));
|
||||
reader.setScaledSize(QSize(nw, nh));
|
||||
}
|
||||
if (!core::image_file::probeImagePixelSize(m_imagePath, &m_sourceLogicalSize)) {
|
||||
m_sourceLogicalSize = QSize();
|
||||
}
|
||||
m_image = reader.read();
|
||||
m_image =
|
||||
core::image_file::loadImageLimited(m_imagePath, core::image_decode::kPreviewMaxPixels);
|
||||
if (m_image.isNull()) {
|
||||
reject();
|
||||
return;
|
||||
}
|
||||
if (!m_sourceLogicalSize.isValid() || m_sourceLogicalSize.width() <= 0 ||
|
||||
m_sourceLogicalSize.height() <= 0) {
|
||||
m_sourceLogicalSize = m_image.size();
|
||||
}
|
||||
}
|
||||
|
||||
void ImageCropDialog::rebuildUi() {
|
||||
auto* root = new QVBoxLayout(this);
|
||||
|
||||
auto* hint = new QLabel(QStringLiteral("拖拽选择裁剪区域(不选则使用整张图)。"), this);
|
||||
auto* hint = new QLabel(
|
||||
QStringLiteral(
|
||||
"默认已框选整张图。滚轮可连续缩放;拖拽边或角调整裁剪,框内拖拽移动选区;选区外或留白处左键拖动平移视图。「重置」恢复整图选区。"
|
||||
" 若已链接 libvips,超大图用流式解码与预览;否则回退 Qt。确定裁剪后坐标按原图逻辑像素对齐。"),
|
||||
this);
|
||||
hint->setWordWrap(true);
|
||||
root->addWidget(hint);
|
||||
|
||||
m_view = new CropView(this);
|
||||
@@ -196,7 +528,7 @@ void ImageCropDialog::rebuildUi() {
|
||||
|
||||
auto* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
|
||||
m_okButton = buttons->button(QDialogButtonBox::Ok);
|
||||
auto* resetBtn = new QPushButton(QStringLiteral("重置选择"), this);
|
||||
auto* resetBtn = new QPushButton(QStringLiteral("重置"), this);
|
||||
buttons->addButton(resetBtn, QDialogButtonBox::ActionRole);
|
||||
|
||||
connect(resetBtn, &QPushButton::clicked, this, &ImageCropDialog::onReset);
|
||||
@@ -213,7 +545,14 @@ QRect ImageCropDialog::selectedRectInImagePixels() const {
|
||||
if (!m_view) {
|
||||
return {};
|
||||
}
|
||||
return m_view->selectionInImagePixels();
|
||||
const QRect inPreview = m_view->selectionInImagePixels();
|
||||
if (!inPreview.isValid() || m_image.isNull()) {
|
||||
return {};
|
||||
}
|
||||
if (m_sourceLogicalSize.isValid() && m_sourceLogicalSize != m_image.size()) {
|
||||
return core::image_decode::mapRectBetweenSizes(inPreview, m_image.size(), m_sourceLogicalSize);
|
||||
}
|
||||
return inPreview;
|
||||
}
|
||||
|
||||
void ImageCropDialog::onReset() {
|
||||
@@ -225,4 +564,3 @@ void ImageCropDialog::onReset() {
|
||||
void ImageCropDialog::onOk() {
|
||||
accept();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user