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