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