/*
Copyright 2006-2026 The QElectroTech Team
This file is part of QElectroTech.
QElectroTech is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
QElectroTech is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with QElectroTech. If not, see .
*/
#ifndef IMAGE_CROP_DIALOG_H
#define IMAGE_CROP_DIALOG_H
#include
#include
#include
#include
class QMouseEvent;
class QPaintEvent;
class QPushButton;
/**
@brief The CropAreaWidget class
Displays the image scaled to fit a reasonable dialog size, with a
draggable, resizable crop rectangle overlaid on top -- the area
outside it dimmed, the usual visual convention for "this part goes
away" during a crop, so it's never ambiguous which region survives.
All internal geometry (the crop rect, drag handling, hit-testing)
works in display-space pixel coordinates; only cropRect() converts
back to the original, full-resolution image's own coordinates for
the caller, the same original-vs-display distinction already used
for colour sampling in ImageTransparentColorDialog.
*/
class CropAreaWidget : public QWidget
{
Q_OBJECT
public:
/// @param sourceImage the full, uncropped image to crop from
/// @param initialCropRect a previously-chosen crop rectangle, in
/// sourceImage's own coordinates, to start from -- an empty
/// (default-constructed) QRect means "no previous crop", i.e.
/// start covering the whole image.
explicit CropAreaWidget(const QImage &sourceImage, const QRect &initialCropRect = QRect(), QWidget *parent = nullptr);
/// The current crop rectangle, in the original image's own
/// coordinates (not display-space) -- always clamped to the
/// image's own bounds and never degenerate (zero width/height).
QRect cropRect() const;
public slots:
/// Resets the crop rectangle back to covering the entire image --
/// the direct answer to "I want to undo the crop, not just
/// adjust it", since dragging every handle back out by hand to
/// exactly the original extent is needlessly fiddly.
void resetToFullImage();
signals:
void cropRectChanged();
protected:
void paintEvent(QPaintEvent *event) override;
void mousePressEvent(QMouseEvent *event) override;
void mouseMoveEvent(QMouseEvent *event) override;
void mouseReleaseEvent(QMouseEvent *event) override;
private:
enum class DragMode { None, Move, ResizeN, ResizeS, ResizeE, ResizeW, ResizeNE, ResizeNW, ResizeSE, ResizeSW };
DragMode handleAt(const QPoint &pos) const;
void applyDrag(const QPoint &pos);
QImage m_source;
qreal m_displayScale = 1.0;
QRect m_displayImageRect; // where the (possibly scaled) pixmap actually sits within this widget
QRect m_cropRect; // in display-space, relative to m_displayImageRect's own origin
DragMode m_dragMode = DragMode::None;
QPoint m_dragStartPos;
QRect m_dragStartRect;
static constexpr int HANDLE_SIZE = 8;
static constexpr int MIN_CROP_SIZE = 16;
};
/**
@brief The ImageCropDialog class
Self-contained modal dialog wrapping CropAreaWidget with OK/Cancel --
the same reasoning as ImageTransparentColorDialog: this needs
neither undo-during-drag nor coexistence with other tools, only a
rectangle the user drags out against a pixmap already in hand.
*/
class ImageCropDialog : public QDialog
{
Q_OBJECT
public:
/// @param pixmap the full, uncropped image to crop from
/// @param existingCropRect a crop rectangle remembered from a
/// previous session, shown from the start rather than resetting
/// to the full image every time the dialog is reopened -- an
/// empty QRect means there isn't one yet.
explicit ImageCropDialog(const QPixmap &pixmap, const QRect &existingCropRect = QRect(), QWidget *parent = nullptr);
/// The chosen crop rectangle, in the original pixmap's own
/// coordinates. Only meaningful after the dialog was accepted.
QRect cropRect() const;
private:
CropAreaWidget *m_cropArea;
};
#endif // IMAGE_CROP_DIALOG_H