1 /* 2 Scan Tailor - Interactive post-processing tool for scanned pages. 3 Copyright (C) Joseph Artsimovich <joseph.artsimovich@gmail.com> 4 5 This program is free software: you can redistribute it and/or modify 6 it under the terms of the GNU General Public License as published by 7 the Free Software Foundation, either version 3 of the License, or 8 (at your option) any later version. 9 10 This program is distributed in the hope that it will be useful, 11 but WITHOUT ANY WARRANTY; without even the implied warranty of 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 GNU General Public License for more details. 14 15 You should have received a copy of the GNU General Public License 16 along with this program. If not, see <http://www.gnu.org/licenses/>. 17 */ 18 19 #ifndef IMAGEVIEWBASE_H_ 20 #define IMAGEVIEWBASE_H_ 21 22 #include <QAbstractScrollArea> 23 #include <QImage> 24 #include <QPixmap> 25 #include <QPoint> 26 #include <QPointF> 27 #include <QRectF> 28 #include <QSizeF> 29 #include <QString> 30 #include <QTimer> 31 #include <QTransform> 32 #include <QWidget> 33 #include <Qt> 34 #include "ImagePixmapUnion.h" 35 #include "ImageViewInfoProvider.h" 36 #include "InteractionHandler.h" 37 #include "InteractionState.h" 38 #include "Margins.h" 39 #include "intrusive_ptr.h" 40 41 class QPainter; 42 class BackgroundExecutor; 43 class ImagePresentation; 44 45 /** 46 * \brief The base class for widgets that display and manipulate images. 47 * 48 * This class operates with 3 coordinate systems: 49 * \li Image coordinates, where m_image.rect() is defined. 50 * \li Pixmap coordinates, where m_pixmap.rect() is defined. 51 * m_pixmap is constructed from a downscaled version of m_image. 52 * \li Virtual image coordinates. We need them because we are not 53 * displaying m_image as is. Instead, we display a pre-transformed 54 * version of it. So, the virtual image coordinates reference the 55 * pixels of an imaginary image that we would get if we actually 56 * pre-transformed m_image the way we want. 57 * \li Widget coordinates, where this->rect() is defined. 58 * 59 * \see m_pixmapToImage, m_imageToVirt, m_virtualToWidget, m_widgetToVirtual. 60 */ 61 class ImageViewBase : public QAbstractScrollArea { 62 Q_OBJECT 63 public: 64 enum FocalPointMode { CENTER_IF_FITS, DONT_CENTER }; 65 66 /** 67 * \brief ImageViewBase constructor. 68 * 69 * \param image The image to display. 70 * \param downscaled_version The downscaled version of \p image. 71 * If it's null, it will be created automatically. 72 * The exact scale doesn't matter. 73 * The whole idea of having a downscaled version is 74 * to speed up real-time rendering of high-resolution 75 * images. Note that the delayed high quality transform 76 * operates on the original image, not the downscaled one. 77 * \param presentation Specifies transformation from image 78 * pixel coordinates to virtual image coordinates, along 79 * with some other properties. 80 * \param margins Reserve extra space near the widget borders. 81 * The units are widget pixels. This reserved area may 82 * be used for custom drawing or custom controls. 83 */ 84 ImageViewBase(const QImage& image, 85 const ImagePixmapUnion& downscaled_version, 86 const ImagePresentation& presentation, 87 const Margins& margins = Margins()); 88 89 ~ImageViewBase() override; 90 91 /** 92 * The idea behind this accessor is being able to share a single 93 * downscaled pixmap between multiple image views. 94 */ downscaledPixmap()95 const QPixmap& downscaledPixmap() const { return m_pixmap; } 96 97 /** 98 * \brief Enable or disable the high-quality transform. 99 */ 100 void hqTransformSetEnabled(bool enabled); 101 102 /** 103 * \brief A stand-alone function to create a downscaled image 104 * to be passed to the constructor. 105 * 106 * The point of using this function instead of letting 107 * the constructor do the job is that this function may 108 * be called from a background thread, while the constructor 109 * can't. 110 * 111 * \param image The input image, not null, and with DPI set correctly. 112 * \return The image downscaled by an unspecified degree. 113 */ 114 static QImage createDownscaledImage(const QImage& image); 115 rootInteractionHandler()116 InteractionHandler& rootInteractionHandler() { return m_rootInteractionHandler; } 117 interactionState()118 InteractionState& interactionState() { return m_interactionState; } 119 interactionState()120 const InteractionState& interactionState() const { return m_interactionState; } 121 imageToVirtual()122 const QTransform& imageToVirtual() const { return m_imageToVirtual; } 123 virtualToImage()124 const QTransform& virtualToImage() const { return m_virtualToImage; } 125 virtualToWidget()126 const QTransform& virtualToWidget() const { return m_virtualToWidget; } 127 widgetToVirtual()128 const QTransform& widgetToVirtual() const { return m_widgetToVirtual; } 129 imageToWidget()130 QTransform imageToWidget() const { return m_imageToVirtual * m_virtualToWidget; } 131 widgetToImage()132 QTransform widgetToImage() const { return m_widgetToVirtual * m_virtualToImage; } 133 update()134 void update() { viewport()->update(); } 135 virtualDisplayRect()136 const QRectF& virtualDisplayRect() const { return m_virtualDisplayArea; } 137 138 /** 139 * Get the bounding box of the image as it appears on the screen, 140 * in widget coordinates. 141 */ 142 QRectF getOccupiedWidgetRect() const; 143 144 /** 145 * \brief A better version of setStatusTip(). 146 * 147 * Unlike setStatusTip(), this method will display the tooltip 148 * immediately, not when the mouse enters the widget next time. 149 */ 150 void ensureStatusTip(const QString& status_tip); 151 152 /** 153 * \brief Get the focal point in widget coordinates. 154 * 155 * The typical usage pattern for this function is: 156 * \code 157 * QPointF fp(obj.getWidgetFocalPoint()); 158 * obj.setWidgetFocalPoint(fp + delta); 159 * \endcode 160 * As a result, the image will be moved by delta widget pixels. 161 */ getWidgetFocalPoint()162 QPointF getWidgetFocalPoint() const { return m_widgetFocalPoint; } 163 164 /** 165 * \brief Set the focal point in widget coordinates. 166 * 167 * This one may be used for unrestricted dragging (with Shift button). 168 * 169 * \see getWidgetFocalPoint() 170 */ 171 void setWidgetFocalPoint(const QPointF& widget_fp); 172 173 /** 174 * \brief Set the focal point in widget coordinates, after adjustring 175 * it to avoid wasting of widget space. 176 * 177 * This one may be used for resticted dragging (the default one in ST). 178 * 179 * \see getWidgetFocalPoint() 180 * \see setWidgetFocalPoint() 181 */ 182 void adjustAndSetWidgetFocalPoint(const QPointF& widget_fp); 183 184 /** 185 * \brief Sets the widget focal point and recalculates the pixmap focal 186 * focal point so that the image is not moved on screen. 187 */ 188 void setWidgetFocalPointWithoutMoving(QPointF new_widget_fp); 189 190 /** 191 * \brief Updates image-to-virtual and recalculates 192 * virtual-to-widget transformations. 193 */ 194 void updateTransform(const ImagePresentation& presentation); 195 196 /** 197 * \brief Same as updateTransform(), but adjusts the focal point 198 * to improve screen space usage. 199 */ 200 void updateTransformAndFixFocalPoint(const ImagePresentation& presentation, FocalPointMode mode); 201 202 /** 203 * \brief Same as updateTransform(), but preserves the visual image scale. 204 */ 205 void updateTransformPreservingScale(const ImagePresentation& presentation); 206 207 /** 208 * \brief Sets the zoom level. 209 * 210 * Zoom level 1.0 means such a zoom that makes the image fit the widget. 211 * Zooming will take into account the current widget and pixmap focal 212 * points. To zoom to a specific point, for example the mouse position, 213 * call setWidgetFocalPointWithoutMoving() first. 214 */ 215 void setZoomLevel(double zoom); 216 217 /** 218 * \brief Returns the current zoom level. 219 * \see setZoomLevel() 220 */ zoomLevel()221 double zoomLevel() const { return m_zoom; } 222 223 /** 224 * The image is considered ideally positioned when as little as possible 225 * screen space is wasted. 226 * 227 * \param pixel_length The euclidean distance in widget pixels to move the image. 228 * Will be clipped if it's more than required to reach the ideal position. 229 */ 230 void moveTowardsIdealPosition(double pixel_length); 231 232 static BackgroundExecutor& backgroundExecutor(); 233 234 ImageViewInfoProvider& infoProvider(); 235 236 protected: 237 void paintEvent(QPaintEvent* event) override; 238 239 void keyPressEvent(QKeyEvent* event) override; 240 241 void keyReleaseEvent(QKeyEvent* event) override; 242 243 void mousePressEvent(QMouseEvent* event) override; 244 245 void mouseReleaseEvent(QMouseEvent* event) override; 246 247 void mouseDoubleClickEvent(QMouseEvent* event) override; 248 249 void mouseMoveEvent(QMouseEvent* event) override; 250 251 void wheelEvent(QWheelEvent* event) override; 252 253 void contextMenuEvent(QContextMenuEvent* event) override; 254 255 void resizeEvent(QResizeEvent* event) override; 256 257 void enterEvent(QEvent* event) override; 258 259 void leaveEvent(QEvent* event) override; 260 261 void showEvent(QShowEvent* event) override; 262 263 /** 264 * Returns the maximum viewport size (as if scrollbars are hidden) 265 * reduced by margins. 266 */ 267 QRectF maxViewportRect() const; 268 269 virtual void updateCursorPos(const QPointF& pos); 270 271 virtual void updatePhysSize(); 272 273 private slots: 274 275 void initiateBuildingHqVersion(); 276 277 void updateScrollBars(); 278 279 void reactToScrollBars(); 280 281 private: 282 class HqTransformTask; 283 class TempFocalPointAdjuster; 284 285 class TransformChangeWatcher; 286 287 QRectF dynamicViewportRect() const; 288 289 void transformChanged(); 290 291 void updateWidgetTransform(); 292 293 void updateWidgetTransformAndFixFocalPoint(FocalPointMode mode); 294 295 QPointF getIdealWidgetFocalPoint(FocalPointMode mode) const; 296 297 void setNewWidgetFP(QPointF widget_fp, bool update = false); 298 299 void adjustAndSetNewWidgetFP(QPointF proposed_widget_fp, bool update = false); 300 301 QPointF centeredWidgetFocalPoint() const; 302 303 bool validateHqPixmap() const; 304 305 void scheduleHqVersionRebuild(); 306 307 void hqVersionBuilt(const QPoint& origin, const QImage& image); 308 309 void updateStatusTipAndCursor(); 310 311 void updateStatusTip(); 312 313 void updateCursor(); 314 315 void maybeQueueRedraw(); 316 317 InteractionHandler m_rootInteractionHandler; 318 319 InteractionState m_interactionState; 320 321 /** 322 * The client-side image. Used to build a high-quality version 323 * for delayed rendering. 324 */ 325 QImage m_image; 326 327 /** 328 * This timer is used for delaying the construction of 329 * a high quality image version. 330 */ 331 QTimer m_timer; 332 333 /** 334 * The image handle. Note that the actual data of a QPixmap lives 335 * in another process on most platforms. 336 */ 337 QPixmap m_pixmap; 338 339 /** 340 * The high quality, pre-transformed version of m_pixmap. 341 */ 342 QPixmap m_hqPixmap; 343 344 /** 345 * The position, in widget coordinates, where m_hqPixmap is to be drawn. 346 */ 347 QPoint m_hqPixmapPos; 348 349 /** 350 * The transformation used to build m_hqPixmap. 351 * It's used to detect if m_hqPixmap needs to be rebuild. 352 */ 353 QTransform m_hqXform; 354 355 /** 356 * Used to check if we need to extend the delay before building m_hqPixmap. 357 */ 358 QTransform m_potentialHqXform; 359 360 /** 361 * The ID (QImage::cacheKey()) of the image that was used 362 * to build m_hqPixmap. It's used to detect if m_hqPixmap 363 * needs to be rebuilt. 364 */ 365 qint64 m_hqSourceId; 366 367 /** 368 * The pending (if any) high quality transformation task. 369 */ 370 intrusive_ptr<HqTransformTask> m_hqTransformTask; 371 372 /** 373 * Transformation from m_pixmap coordinates to m_image coordinates. 374 */ 375 QTransform m_pixmapToImage; 376 377 /** 378 * The area of the virtual image to be displayed. 379 * Everything outside of it will be cropped. 380 */ 381 QPolygonF m_virtualImageCropArea; 382 383 /** 384 * The area in virtual image coordinates to be displayed. 385 * The idea is that it can be larger than m_virtualImageCropArea 386 * to reserve space for custom drawing or controls. 387 */ 388 QRectF m_virtualDisplayArea; 389 390 /** 391 * A transformation from original to virtual image coordinates. 392 */ 393 QTransform m_imageToVirtual; 394 395 /** 396 * A transformation from virtual to original image coordinates. 397 */ 398 QTransform m_virtualToImage; 399 400 /** 401 * Transformation from virtual image coordinates to widget coordinates. 402 */ 403 QTransform m_virtualToWidget; 404 405 /** 406 * Transformation from widget coordinates to virtual image coordinates. 407 */ 408 QTransform m_widgetToVirtual; 409 410 /** 411 * Transforms scroll bar values to corresponding positions of the display 412 * area (its central point) in widget coordinates. 413 */ 414 QTransform m_scrollTransform; 415 416 /** 417 * An arbitrary point in widget coordinates that corresponds 418 * to m_pixmapFocalPoint in m_pixmap coordinates. 419 * Moving m_widgetFocalPoint followed by updateWidgetTransform() 420 * will cause the image to move on screen. 421 */ 422 QPointF m_widgetFocalPoint; 423 424 /** 425 * An arbitrary point in m_pixmap coordinates that corresponds 426 * to m_widgetFocalPoint in widget coordinates. 427 * Unlike m_widgetFocalPoint, this one is not supposed to be 428 * moved independently. It's supposed to moved together with 429 * m_widgetFocalPoint for zooming into a specific position. 430 */ 431 QPointF m_pixmapFocalPoint; 432 433 /** 434 * Used to distinguish between resizes induced by scrollbars (dis)appearing 435 * and other factors. 436 */ 437 QSize m_lastMaximumViewportSize; 438 439 /** 440 * The number of pixels to be left blank at each side of the widget. 441 */ 442 Margins m_margins; 443 444 /** 445 * The zoom factor. A value of 1.0 corresponds to fit-to-widget zoom. 446 */ 447 double m_zoom; 448 449 /** 450 * This timer is used for tracking the cursor position. 451 */ 452 QTimer m_cursorTrackerTimer; 453 454 /** 455 * Current mouse pos in widget coordinates. 456 */ 457 QPointF m_cursorPos; 458 459 int m_transformChangeWatchersActive; 460 461 int m_ignoreScrollEvents; 462 463 int m_ignoreResizeEvents; 464 465 bool m_hqTransformEnabled; 466 467 ImageViewInfoProvider m_infoProvider; 468 }; 469 470 471 #endif // ifndef IMAGEVIEWBASE_H_ 472