1 /************************************************** -*- mode:c++; -*- ***
2  *									*
3  *  This file is part of libkscan, a KDE scanning library.		*
4  *									*
5  *  Copyright (C) 2013 Jonathan Marten <jjm@keelhaul.me.uk>		*
6  *  Copyright (C) 1999 Klaas Freitag <freitag@suse.de>			*
7  *									*
8  *  This library is free software; you can redistribute it and/or	*
9  *  modify it under the terms of the GNU Library General Public		*
10  *  License as published by the Free Software Foundation and appearing	*
11  *  in the file COPYING included in the packaging of this file;		*
12  *  either version 2 of the License, or (at your option) any later	*
13  *  version.								*
14  *									*
15  *  This program is distributed in the hope that it will be useful,	*
16  *  but WITHOUT ANY WARRANTY; without even the implied warranty of	*
17  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the	*
18  *  GNU General Public License for more details.			*
19  *									*
20  *  You should have received a copy of the GNU General Public License	*
21  *  along with this program;  see the file COPYING.  If not, write to	*
22  *  the Free Software Foundation, Inc., 51 Franklin Street,		*
23  *  Fifth Floor, Boston, MA 02110-1301, USA.				*
24  *									*
25  ************************************************************************/
26 
27 #ifndef IMAGECANVAS_H
28 #define IMAGECANVAS_H
29 
30 #include "kookascan_export.h"
31 
32 #include <qgraphicsview.h>
33 #include <qvector.h>
34 
35 class QGraphicsScene;
36 class QGraphicsPixmapItem;
37 
38 class QMenu;
39 
40 class SelectionItem;
41 
42 /**
43  * @short Image display canvas widget.
44  *
45  * Displays a scalable image in a scrolling area, along with an interactive
46  * selection area and any number of optional highlight areas.
47  *
48  * The selection area is used for selecting a part of the image for scanning
49  * or cropping, while the highlight areas are used for indicating words
50  * while performing OCR spell checking.
51  *
52  * @author Klaas Freitag
53  * @author Jonathan Marten
54  **/
55 
56 class KOOKASCAN_EXPORT ImageCanvas : public QGraphicsView
57 {
58     Q_OBJECT
59 
60 public:
61     /**
62      * Create the image canvas widget.
63      *
64      * @param parent The parent widget.
65      * @param start_image The initial image to display.  If this is
66      * not specified, nothing is displayed until an image is set
67      * using @c newImage().
68      **/
69     explicit ImageCanvas(QWidget *parent = nullptr, const QImage *start_image = nullptr);
70 
71     /**
72      * Destructor.
73      **/
74     ~ImageCanvas() override;
75 
76     /**
77      * Scaling types for the image as displayed on the canvas.
78      **/
79     enum ScaleType {
80         ScaleUnspecified,           /**< No scale specified */
81         ScaleDynamic,               /**< Best fit */
82         ScaleOriginal,              /**< Original size */
83         ScaleFitWidth,              /**< Fit to width */
84         ScaleFitHeight,             /**< Fit to height */
85         ScaleZoom               /**< Fit to arbitrary zoom */
86     };
87 
88     /**
89      * Request for the canvas to perform an action.  This will usually be
90      * as a result of a user action in the application.
91      *
92      * @see ImgScaleDialog
93      **/
94     enum UserAction {
95         UserActionZoom,             /**< Display an @c ImgScaleDialog to set a zoom factor */
96         UserActionFitWidth,         /**< Scale the image to fit the width */
97         UserActionFitHeight,            /**< Scale the image to fit the height */
98         UserActionOrigSize,         /**< Reset the scale to the original image size */
99         UserActionClose,            /**< Emit the @c closingRequested() signal */
100     };
101 
102     /**
103      * The style used to draw a highlight box.
104      **/
105     enum HighlightStyle {
106         HighlightBox,               /**< A rectangular box */
107         HighlightUnderline          /**< A line along the bottom box edge */
108     };
109 
110     /**
111      * Get a context menu for the image canvas.
112      *
113      * The menu is created on demand, and the first time that this function
114      * is called an empty menu will be returned.  The calling application
115      * must populate the menu with actions and handle them when they are
116      * triggered;  if an action on the canvas is required, it should request
117      * them via @c slotUserAction().  Do not delete the returned menu,
118      * it is owned by the @c ImageCanvas object.
119      *
120      * @return The context menu
121      * @see slotUserAction
122      **/
123     QMenu *contextMenu();
124 
125     /**
126      * Set the brightness of the displayed image.
127      *
128      * @param b The new brightness value
129      * @note Not currently implemented, the value specified will be ignored.
130      **/
setBrightness(int b)131     void setBrightness(int b)
132     {
133         mBrightness = b;
134     }
135 
136     /**
137      * Set the contrast of the displayed image.
138      *
139      * @param c The new contrast value
140      * @note Not currently implemented, the value specified will be ignored.
141      **/
setContrast(int c)142     void setContrast(int c)
143     {
144         mContrast = c;
145     }
146 
147     /**
148      * Set the gamma of the displayed image.
149      *
150      * @param g The new gamma value
151      * @note Not currently implemented, the value specified will be ignored.
152      **/
setGamma(int g)153     void setGamma(int g)
154     {
155         mGamma = g;
156     }
157 
158     /**
159      * Get the current brightness setting.
160      *
161      * @return The brightness value
162      * @note Not currently implemented, an undefined value will be returned.
163      **/
getBrightness()164     int getBrightness() const
165     {
166         return (mBrightness);
167     }
168 
169     /**
170      * Get the current contrast setting.
171      *
172      * @return The contrast value
173      * @note Not currently implemented, an undefined value will be returned.
174      **/
getContrast()175     int getContrast() const
176     {
177         return (mContrast);
178     }
179 
180     /**
181      * Get the current gamma setting.
182      *
183      * @return The gamma value
184      * @note Not currently implemented, an undefined value will be returned.
185      **/
getGamma()186     int getGamma() const
187     {
188         return (mGamma);
189     }
190 
191     /**
192      * Set the scale factor to be used for display.  The image is immediately
193      * resized in accordance with the scale factor.
194      *
195      * @param f The scale factor as a percentage. 100 means original size,
196      * while 0 means dynamic (best fit) scaling.
197      **/
198     void setScaleFactor(int f);
199 
200     /**
201      * Get the scale factor currently in use.
202      *
203      * @return The current scale factor as a percentage.
204      * Original size scaling returns a value of 100, dynamic
205      * scaling returns a value of 0.
206      **/
getScaleFactor()207     int getScaleFactor() const
208     {
209         return (mScaleFactor);
210     }
211 
212     /**
213      * Set the scale type to be used for display.  The image is immediately
214      * resized in accordance with the scale type.
215      *
216      * @param type The new scale type to be set
217      * @note Do not use this function to set a @p type of @c ScaleZoom.
218      * Use @c setScaleFactor with the required scale factor instead.
219      * @see ScaleType
220      **/
221     void setScaleType(ImageCanvas::ScaleType type);
222 
223     /**
224      * Get the current scaling type in use.
225      *
226      * @return The scale type
227      * @see ScaleType
228      **/
229     ImageCanvas::ScaleType scaleType() const;
230 
231     /**
232      * Get a textual description for the current scaling type.
233      *
234      * @return An I18N'ed description
235      **/
236     const QString scaleTypeString() const;
237 
238     /**
239      * Set the default scaling type that will be applied to a new image,
240      * either on construction or after setting a new image without the
241      * zoom hold in effect.
242      *
243      * @param type The new default scale type
244      * @note If this function is not used. the default scaling type is @c Original.
245      * @note It is not useful to use this function to set a @p type of @c ScaleZoom.
246      **/
setDefaultScaleType(ImageCanvas::ScaleType type)247     void setDefaultScaleType(ImageCanvas::ScaleType type)
248     {
249         mDefaultScaleType = type;
250     }
251 
252     /**
253      * Get the default scaling type that will be applied to a new image.
254      *
255      * @return The default scale type
256      **/
defaultScaleType()257     ImageCanvas::ScaleType defaultScaleType() const
258     {
259         return (mDefaultScaleType);
260     }
261 
262     /**
263      * Access the image displayed.
264      *
265      * @return The image, or @c nullptr if no image is currently set.
266      **/
rootImage()267     const QImage *rootImage() const
268     {
269         return (mImage);
270     }
271 
272     /**
273      * Check whether an image is currently set and displayed
274      *
275      * @return @c true if an image is currently set
276      **/
277     bool hasImage() const;
278 
279     /**
280      * Check whether the image canvas is read-only.
281      *
282      * @return @c true if the image is read-only, @c false if it allows interaction.
283      * @see setReadOnly
284      * @see imageReadOnly
285      **/
isReadOnly()286     bool isReadOnly() const
287     {
288         return (mReadOnly);
289     }
290 
291     /**
292      * Get the bounds of the currently selected area, in absolute image pixels.
293      *
294      * @return The selected rectangle, in source image pixels.
295      * If there is no selection, a null (invalid) rectangle is returned.
296      * @see setSelectionRect
297      * @see QRect::isValid()
298      **/
299     QRect selectedRect() const;
300 
301     /**
302      * Get the bounds of the currently selected area, as a scaled proportion of
303      * the image size.
304      *
305      * @return The selected rectangle, scaled to the image size:
306      * for example, 0.5 means 50% of the image width or height.
307      * If there is no selection, a null (invalid) rectangle is returned.
308      * @see setSelectionRect
309      * @see QRectF::isValid()
310      **/
311     QRectF selectedRectF() const;
312 
313     /**
314      * Check whether there is a currently selected area.
315      *
316      * @return @c true if there is a selection.
317      * @see selectedRect
318      * @see selectedRectF
319      **/
320     bool hasSelectedRect() const;
321 
322     /**
323      * Set a new selected area, in absolute image pixels.
324      *
325      * @param rect The new selected rectangle, in source image pixels.
326      * @see selectedRect
327      **/
328     void setSelectionRect(const QRect &rect);
329 
330     /**
331      * Set a new selected area, as a scaled proportion of
332      * the image size.
333      *
334      * @param rect The new selected rectangle, in source image pixels:
335      * for example, 0.5 means 50% of the image width or height.
336      * @see selectedRectF
337      **/
338     void setSelectionRect(const QRectF &rect);
339 
340     /**
341      * Get a copy of the selected image area.
342      *
343      * @return A copy of the selected image area, or a null image if there
344      * is no image displayed or if there is no selected area.
345      **/
346     QImage selectedImage() const;
347 
348     /**
349      * Get a textual description of the image size and depth.
350      *
351      * @return The I18N'ed description, in the format "WxH pixels, D bpp",
352      * or "-" if no image is set.
353      **/
354     const QString imageInfoString() const;
355 
356     /**
357      * As above, but return a description for the specified
358      * width, height and depth.
359      *
360      * @param w Width
361      * @param h Height
362      * @param d Bit depth
363      * @return The description
364      **/
365     static const QString imageInfoString(int w, int h, int d);
366 
367     /**
368      * As above, but return a description for the specified image.
369      *
370      * @param img The image
371      * @return Its description
372      **/
373     static const QString imageInfoString(const QImage *img);
374 
375     /**
376      * Display a new image.
377      *
378      * The old image is forgotten (but not deleted).  The selection is
379      * cleared, but the newRect() signals are not emitted.  All current
380      * highlights are removed.  Unless the @p hold_zoom option is set
381      * or @c setKeepZoom(true) has been called, the scaling type is
382      * reset to the default.
383      *
384      * @param new_image The new image to display.  If this is @c nullptr,
385      * no new image is set.
386      * @param hold_zoom If set to @c true, do not change the current
387      * scaling type or scaling factor;  if set to @c false, reset the
388      * scaling type to that set by @c setDefaultScaleType().
389      * @see setDefaultScaleType
390      * @see setKeepZoom
391      **/
392     void newImage(const QImage *new_image, bool hold_zoom = false);
393 
394     /**
395      * Highlight a rectangular area on the current image,
396      * using the previously specified style, brush and pen.
397      *
398      * @param rect The rectangle to be highlighted.  Unlike the selection
399      * rectangle, this is specified in absolute image pixels.
400      * @param ensureVis If set to @c true, the new highlight rectangle
401      * will be made visible by scrolling to it if necessary.
402      * @return The new highlight ID.
403      * @see removeHighlight
404      * @see setHighlightStyle
405      * @see scrollTo
406      */
407     int addHighlight(const QRect &rect, bool ensureVis = false);
408 
409     /**
410      * Remove a highlight from the image.
411      *
412      * @param id The ID of the highlight to be removed.
413      * @see addHighlight
414      */
415     void removeHighlight(int id);
416 
417     /**
418      * Remove all highlights from the image.
419      *
420      * @see addHighlight
421      */
422     void removeAllHighlights();
423 
424     /**
425      * Set the style, pen and brush to be used for subsequently
426      * added highlight rectangles.
427      *
428      * @param style The new style
429      * @param pen The new line pen
430      * @param brush The new fill brush
431      * @note The default @p style is HighlightBox, but there are no defaults
432      * for @p pen or @p brush.
433      * @note The @p pen is always forced to be cosmetic.
434      * @see QPen::setCosmetic
435      **/
436     void setHighlightStyle(ImageCanvas::HighlightStyle style, const QPen &pen = QPen(), const QBrush &brush = QBrush());
437 
438     /**
439      * Scroll if necessary so that the specified rectangle is visible.
440      * The rectangle is specified in absolute image pixels.
441      *
442      * @param rect The rectangle area of interest
443      * @see QGraphicsView::ensureVisible
444      **/
445     void scrollTo(const QRect &rect);
446 
447 public slots:
448     /**
449      * Set whether the current scaling settings are retained when a
450      * new image is set.
451      *
452      * @param k The new setting.  If @c true, the scaling settings are always
453      * retained when setting a new image, regardless of the @c hold_zoom
454      * parameter to @c newImage().  The default is @c false.
455      * @see newImage
456      **/
setKeepZoom(bool k)457     void setKeepZoom(bool k)
458     {
459         mKeepZoom = k;
460     }
461 
462     /**
463      * Set whether the aspect ratio of the image is maintained for
464      * dynamic scaling mode.
465      *
466      * @param ma The new setting.  If @c true, the aspect ratio of the
467      * image will be maintained;  if @c false, the width and height will
468      * be scaled independently for the best fit.  The default is @c true.
469      **/
setMaintainAspect(bool ma)470     void setMaintainAspect(bool ma)
471     {
472         mMaintainAspect = ma;
473     }
474 
475     /**
476      * Set whether the image is considered to be read-only or whether
477      * it allows user interaction.  Setting the read-only status using
478      * this function causes the imageReadOnly() signal to be emitted.
479      *
480      * @param ro If set to @c true, the image is set to be read-only.
481      * The default is @c false allowing user interaction.
482      * @note If the image is read-only mouse actions are ignored and the
483      * selection cannot be set interactively, although it can still be set
484      * by calling the appropriate functions.
485      * @see readOnly
486      * @see imageReadOnly
487      **/
488     void setReadOnly(bool ro);
489 
490     /**
491      * Perform a user-requested action on the image.
492      *
493      * @param act The action to be performed
494      * @note For all scaling setting actions (i.e. all apart
495      * from @c UserActionClose), the image is repainted at
496      * the new setting.  The @c UserActionClose action emits
497      * the @c closingRequested() signal.
498      * @see UserAction
499      * @see closingRequested
500      **/
501     void performUserAction(ImageCanvas::UserAction act);
502 
503 signals:
504     /**
505      * Emitted when a new selection rectangle is created by the user
506      * dragging an area.  The rectangle is provided as absolute image pixels.
507      *
508      * @param rect The new rectangle, the same area and in the same units
509      * as would be returned by @c selectedRect(), or a null @c QRect if
510      * there is no selection.
511      * @see selectedRect
512      **/
513     void newRect(const QRect &rect);
514 
515     /**
516      * Emitted when a new selection rectangle is created by the user
517      * dragging an area.  The rectangle is provided as a proportion of
518      * the source image size.
519      *
520      * @param rect The new rectangle, the same area and in the same units
521      * as would be returned by @c selectedRectF(), or a null @c QRectF if
522      * there is no selection.
523      * @see selectedRectF
524      **/
525     void newRect(const QRectF &rect);
526 
527     /**
528      * Emitted when the user requests to close the image,
529      * by calling @c slotUserAction() with @c UserActionClose.
530      **/
531     void closingRequested();
532 
533     /**
534      * Emitted when the scaling type is set by @c setScaleType().
535      * Can be monitored by the calling application in order to set
536      * a status bar indicator or similar.
537      *
538      * @param scaleType The new scale type as a string, as would
539      * be returned by scaleTypeString().
540      * @see scaleTypeString
541      * @see setScaleFactor
542      **/
543     void scalingChanged(const QString &scaleType);
544 
545     /**
546      * Emitted when the read-only state of the current image changes.
547      *
548      * @param isRO Set to @c true if the image is now read-only,
549      * or @c false if it is not.
550      */
551     void imageReadOnly(bool isRO);
552 
553     /**
554      * Emitted for a mouse double-click over the image.
555      *
556      * @param p The clicked point, in absolute image pixel coordinates.
557      * @note This signal is emitted even if the image is read-only.
558      */
559     void doubleClicked(const QPoint &p);
560 
561 protected:
562     /**
563      * Update and redraw the "marching ants" area rectangle.
564      *
565      * @param ev The timer event
566      **/
567     void timerEvent(QTimerEvent *ev) override;
568 
569     /**
570      * Resize and redraw the currently displayed image to fit the new size.
571      *
572      * @param ev The resize event
573      **/
574     void resizeEvent(QResizeEvent *ev) override;
575 
576     /**
577      * If a context menu has been created by calling @c contextMenu(),
578      * then pop it up at the current mouse position.  If there is no
579      * context menu, then do nothing.
580      *
581      * @param ev The menu event
582      **/
583     void contextMenuEvent(QContextMenuEvent *ev) override;
584 
585     /**
586      * Possibly start to drag a new selection area, or move or resize
587      * the current area.
588      *
589      * @param ev The mouse event
590      **/
591     void mousePressEvent(QMouseEvent *ev) override;
592 
593     /**
594      * Finish dragging a selection area, and emit the @c newRect signals.
595      *
596      * @param ev The mouse event
597      * @see newRect
598      **/
599     void mouseReleaseEvent(QMouseEvent *ev) override;
600 
601     /**
602      * Continue to drag and redraw the selection area.
603      *
604      * @param ev The mouse event
605      **/
606     void mouseMoveEvent(QMouseEvent *ev) override;
607 
608     /**
609      * Detect a double click on the image and emit a signal with
610      * the image coordinates.
611      *
612      * @param ev The mouse event
613      * @see doubleClicked
614      **/
615     void mouseDoubleClickEvent(QMouseEvent *ev) override;
616 
617 private:
618     enum MoveState {
619         MoveNone,
620         MoveTopLeft,
621         MoveTopRight,
622         MoveBottomLeft,
623         MoveBottomRight,
624         MoveLeft,
625         MoveRight,
626         MoveTop,
627         MoveBottom,
628         MoveWhole,
629         MoveNew
630     };
631 
632     void startMarqueeTimer();
633     void stopMarqueeTimer();
634 
635     ImageCanvas::MoveState classifyPoint(const QPoint &p) const;
636     void setCursorShape(Qt::CursorShape cs);
637     void recalculateViewScale();
638 
639     QMenu *mContextMenu;
640     int mTimerId;
641 
642     const QImage *mImage;
643 
644     int mScaleFactor;
645     int mBrightness;
646     int mContrast;
647     int mGamma;
648 
649     bool mMaintainAspect;
650 
651     ImageCanvas::MoveState mMoving;
652     Qt::CursorShape mCurrentCursor;
653 
654     bool mKeepZoom;                 // keep zoom setting if image changes
655     bool mReadOnly;
656 
657     ImageCanvas::ScaleType mScaleType;
658     ImageCanvas::ScaleType mDefaultScaleType;
659 
660     QGraphicsScene *mScene;             // internal graphics scene
661     QGraphicsPixmapItem *mPixmapItem;           // item for background pixmap
662     SelectionItem *mSelectionItem;          // item for selection box
663 
664     QPoint mStartPoint;                 // start point of drag
665     QPoint mLastPoint;                  // previous point of drag
666 
667     QVector<QGraphicsItem *> mHighlights;       // list of highlight rectangles
668 
669     ImageCanvas::HighlightStyle mHighlightStyle;    // for newly created highlights
670     QPen mHighlightPen;
671     QBrush mHighlightBrush;
672 };
673 
674 #endif							// IMAGECANVAS_H
675