diff --git a/ksanetwain/ktwain_widget.cpp b/ksanetwain/ktwain_widget.cpp --- a/ksanetwain/ktwain_widget.cpp +++ b/ksanetwain/ktwain_widget.cpp @@ -190,5 +190,6 @@ void KSaneWidget::setOptionsCollapsed(bool) {} void KSaneWidget::setScanButtonHidden(bool) {} void KSaneWidget::initGetDeviceList() const {} - +void KSaneWidget::enableDisplayLastScan(bool) {} +bool KSaneWidget::displayLastScan() {} } // NameSpace KSaneIface diff --git a/src/ksanepreviewimagebuilder.h b/src/ksanepreviewimagebuilder.h --- a/src/ksanepreviewimagebuilder.h +++ b/src/ksanepreviewimagebuilder.h @@ -34,6 +34,7 @@ } class QImage; +class QByteArray; namespace KSaneIface { @@ -44,7 +45,8 @@ void start(const SANE_Parameters ¶ms); void beginFrame(const SANE_Parameters ¶ms); - bool copyToImage(const SANE_Byte readData[], int read_bytes); + // copy a chunk of data to QImage and optionally - to QByteArray with respect to format + bool copyToImage(const SANE_Byte readData[], int read_bytes, QByteArray *retData = nullptr); bool imageResized(); private: diff --git a/src/ksanepreviewimagebuilder.cpp b/src/ksanepreviewimagebuilder.cpp --- a/src/ksanepreviewimagebuilder.cpp +++ b/src/ksanepreviewimagebuilder.cpp @@ -61,7 +61,7 @@ } m_img->fill(0xFFFFFFFF); } - m_imageResized = false; + m_imageResized = true; } void KSanePreviewImageBuilder::beginFrame(const SANE_Parameters ¶ms) @@ -85,12 +85,26 @@ #define index_blue8_to_argb8(i) (i*4) #define index_blue16_to_argb8(i) (i*2) -bool KSanePreviewImageBuilder::copyToImage(const SANE_Byte readData[], int read_bytes) +// templates below required to return a raw data +// and just duplicate templates in KSaneScanThread +#define index_red8_to_rgb8(i) (i*3) +#define index_red16_to_rgb16(i) ((i/2)*6 + i%2) + +#define index_green8_to_rgb8(i) (i*3 + 1) +#define index_green16_to_rgb16(i) ((i/2)*6 + i%2 + 2) + +#define index_blue8_to_rgb8(i) (i*3 + 2) +#define index_blue16_to_rgb16(i) ((i/2)*6 + i%2 + 4) + +bool KSanePreviewImageBuilder::copyToImage(const SANE_Byte readData[], int read_bytes, QByteArray* retData) { int index; uchar *imgBits = m_img->bits(); switch (m_params.format) { case SANE_FRAME_GRAY: + if (retData) { + retData->append((const char *)readData, read_bytes); + } if (m_params.depth == 1) { int i, j; for (i = 0; i < read_bytes; i++) { @@ -152,6 +166,15 @@ break; case SANE_FRAME_RGB: + + if (m_params.depth == 1) { + break; + } + if (retData) { + retData->append((const char *)readData, read_bytes); + } + + if (m_params.depth == 8) { for (int i = 0; i < read_bytes; i++) { m_px_colors[m_px_c_index] = readData[i]; @@ -201,6 +224,9 @@ imgBits = m_img->bits(); } imgBits[index_red8_to_argb8(m_frameRead)] = readData[i]; + if (retData) { + (*retData)[index_red8_to_rgb8(m_frameRead)] = readData[i]; + } m_frameRead++; } return true; @@ -213,6 +239,9 @@ } imgBits[index_red16_to_argb8(m_frameRead)] = readData[i + 1]; } + if (retData) { + (*retData)[index_red16_to_rgb16(m_frameRead)] = readData[i]; + } m_frameRead++; } return true; @@ -227,6 +256,9 @@ imgBits = m_img->bits(); } imgBits[index_green8_to_argb8(m_frameRead)] = readData[i]; + if (retData) { + (*retData)[index_green8_to_rgb8(m_frameRead)] = readData[i]; + } m_frameRead++; } return true; @@ -239,6 +271,9 @@ } imgBits[index_green16_to_argb8(m_frameRead)] = readData[i + 1]; } + if (retData) { + (*retData)[index_green16_to_rgb16(m_frameRead)] = readData[i]; + } m_frameRead++; } return true; @@ -253,6 +288,9 @@ imgBits = m_img->bits(); } imgBits[index_blue8_to_argb8(m_frameRead)] = readData[i]; + if (retData) { + (*retData)[index_blue8_to_rgb8(m_frameRead)] = readData[i]; + } m_frameRead++; } return true; @@ -265,6 +303,9 @@ } imgBits[index_blue16_to_argb8(m_frameRead)] = readData[i + 1]; } + if (retData) { + (*retData)[index_blue16_to_rgb16(m_frameRead)] = readData[i]; + } m_frameRead++; } return true; diff --git a/src/ksanescanthread.h b/src/ksanescanthread.h --- a/src/ksanescanthread.h +++ b/src/ksanescanthread.h @@ -38,6 +38,10 @@ #include #include +#include +#include + +#include "ksanepreviewimagebuilder.h" #define SCAN_READ_CHUNK_SIZE 100000 @@ -54,17 +58,26 @@ READ_READY } ReadStatus; - KSaneScanThread(SANE_Handle handle, QByteArray *data); + KSaneScanThread(SANE_Handle handle, QByteArray *data, QImage *img = nullptr); void run() override; void setImageInverted(bool); void cancelScan(); int scanProgress(); bool saneStartDone(); + // used by KSaneWidget to check if reading of a new chunk changed QImage size + // has sense only if setDisplayLastScan set to true + bool imageResized(); + + // inform QThread that data should be displayed in QImage as well as returned as raw data. + void setDisplayLastScan(bool); + ReadStatus frameStatus(); SANE_Status saneStatus(); SANE_Parameters saneParameters(); + QMutex imgMutex; + private: void readData(); void copyToScanData(int readBytes); @@ -81,6 +94,10 @@ ReadStatus m_readStatus; bool m_saneStartDone; bool m_invertColors; + // has sense only if setDisplayLastScan is set to true + // required to prepare optional QImage with scan result + std::unique_ptr m_imageBuilder; + bool m_displayLastScan; }; } diff --git a/src/ksanescanthread.cpp b/src/ksanescanthread.cpp --- a/src/ksanescanthread.cpp +++ b/src/ksanescanthread.cpp @@ -33,7 +33,7 @@ namespace KSaneIface { -KSaneScanThread::KSaneScanThread(SANE_Handle handle, QByteArray *data): +KSaneScanThread::KSaneScanThread(SANE_Handle handle, QByteArray *data, QImage *img): QThread(), m_data(data), m_saneHandle(handle), @@ -44,7 +44,9 @@ m_saneStatus(SANE_STATUS_GOOD), m_readStatus(READ_READY), m_saneStartDone(false), - m_invertColors(false) + m_invertColors(false), + m_imageBuilder(new KSanePreviewImageBuilder(img)), + m_displayLastScan(false) {} void KSaneScanThread::setImageInverted(bool inverted) @@ -57,6 +59,11 @@ return m_saneStatus; } +void KSaneScanThread::setDisplayLastScan(bool val) +{ + m_displayLastScan = val; +} + KSaneScanThread::ReadStatus KSaneScanThread::frameStatus() { return m_readStatus; @@ -118,6 +125,10 @@ m_data->reserve(m_dataSize); } + if (m_displayLastScan) { + m_imageBuilder->start(m_params); + } + m_frameRead = 0; m_frame_t_count = 0; m_readStatus = READ_ON_GOING; @@ -188,6 +199,9 @@ return; } //qDebug() << "New Frame"; + if (m_displayLastScan) { + m_imageBuilder->beginFrame(m_params); + } m_frameRead = 0; m_frame_t_count++; break; @@ -200,6 +214,7 @@ } copyToScanData(readBytes); + } #define index_red8_to_rgb8(i) (i*3) @@ -213,6 +228,11 @@ void KSaneScanThread::copyToScanData(int readBytes) { + std::unique_ptr locker_ptr; + if (m_displayLastScan) { + locker_ptr.reset(new QMutexLocker(&imgMutex)); + } + if (m_invertColors) { if (m_params.depth == 16) { //if (readBytes%2) qDebug() << "readBytes=" << readBytes; @@ -230,6 +250,16 @@ } } } + + if (m_displayLastScan) { + if (m_imageBuilder->copyToImage(m_readData, readBytes, m_data)) { + m_frameRead += readBytes; + } else { + m_readStatus = READ_ERROR; + } + return; + } + switch (m_params.format) { case SANE_FRAME_GRAY: m_data->append((const char *)m_readData, readBytes); @@ -304,4 +334,12 @@ return m_saneStartDone; } +bool KSaneScanThread::imageResized() +{ + if (m_displayLastScan) { + return m_imageBuilder->imageResized(); + } + return false; +} + } // NameSpace KSaneIface diff --git a/src/ksaneviewer.h b/src/ksaneviewer.h --- a/src/ksaneviewer.h +++ b/src/ksaneviewer.h @@ -41,7 +41,9 @@ explicit KSaneViewer(QImage *img, QWidget *parent = nullptr); ~KSaneViewer(); - void setQImage(QImage *img); + // is_preview_img defines the type of image (preview or last scan) + // clear_selection should be true if only new preview image is set that requires new content detection + void setQImage(QImage *img, bool is_preview_img = true, bool clear_selection = true); void updateImage(); /** Find selections in the picture * \param area this parameter determine the area of the reduced sized image. */ @@ -66,6 +68,8 @@ void clearActiveSelection(); void clearSavedSelections(); void clearSelections(); + // defines if viewer display preview image or last scanned image + void setDisplayingPreview(bool is_preview); /** This function is used to darken everything except what is inside the given area. * \note all parameters must be in the range 0.0 -> 1.0. diff --git a/src/ksaneviewer.cpp b/src/ksaneviewer.cpp --- a/src/ksaneviewer.cpp +++ b/src/ksaneviewer.cpp @@ -51,9 +51,13 @@ QGraphicsScene *scene; SelectionItem *selection; QImage *img; + //used for selections as viewer can now display two kind of images and they work for preview image only + QSize preview_img_size; QList selectionList; SelectionItem::Intersects change; + // defines if viewer display preview image or last scanned image + bool m_displaying_preview; QPointF lastSPoint; int m_left_last_x; @@ -92,6 +96,7 @@ d->selection->setMaxBottom(img->height()); d->selection->setRect(d->scene->sceneRect()); d->selection->setVisible(false); + d->m_displaying_preview = true; d->hideTop = new QGraphicsRectItem; d->hideBottom = new QGraphicsRectItem; @@ -167,14 +172,16 @@ } // ------------------------------------------------------------------------ -void KSaneViewer::setQImage(QImage *img) +void KSaneViewer::setQImage(QImage *img, bool is_preview_img, bool clear_selection) { if (img == nullptr) { return; } - // remove selections - clearSelections(); + if (clear_selection) { + // remove selections + clearSelections(); + } // clear zoom setMatrix(QMatrix()); @@ -183,6 +190,10 @@ d->selection->setMaxRight(img->width()); d->selection->setMaxBottom(img->height()); d->img = img; + + if (is_preview_img) { + d->preview_img_size = img->size(); + } } // ------------------------------------------------------------------------ @@ -231,6 +242,9 @@ void KSaneViewer::zoom2Fit() { fitInView(d->img->rect(), Qt::KeepAspectRatio); + if (d->m_displaying_preview) { + return; + } d->selection->saveZoom(transform().m11()); for (int i = 0; i < d->selectionList.size(); ++i) { d->selectionList[i]->saveZoom(transform().m11()); @@ -288,11 +302,14 @@ // ------------------------------------------------------------------------ void KSaneViewer::setSelection(float tl_x, float tl_y, float br_x, float br_y) { + int img_width = d->preview_img_size.width(); + int img_height = d->preview_img_size.height(); + QRectF rect; - rect.setCoords(tl_x * d->img->width(), - tl_y * d->img->height(), - br_x * d->img->width(), - br_y * d->img->height()); + rect.setCoords(tl_x * img_width, + tl_y * img_height, + br_x * img_width, + br_y * img_height); d->selection->setRect(rect); updateSelVisibility(); @@ -301,43 +318,49 @@ // ------------------------------------------------------------------------ void KSaneViewer::setHighlightArea(float tl_x, float tl_y, float br_x, float br_y) { + int img_width = d->preview_img_size.width(); + int img_height = d->preview_img_size.height(); + QRectF rect; // Left reason for rect: setCoords(x1,y1,x2,y2) != setRect(x1,x2, width, height) - rect.setCoords(0, 0, tl_x * d->img->width(), d->img->height()); + rect.setCoords(0, 0, tl_x * img_width, img_height); d->hideLeft->setRect(rect); // Right - rect.setCoords(br_x * d->img->width(), + rect.setCoords(br_x * img_width, 0, - d->img->width(), - d->img->height()); + img_width, + img_height); d->hideRight->setRect(rect); // Top - rect.setCoords(tl_x * d->img->width(), + rect.setCoords(tl_x * img_width, 0, - br_x * d->img->width(), - tl_y * d->img->height()); + br_x * img_width, + tl_y * img_height); d->hideTop->setRect(rect); // Bottom - rect.setCoords(tl_x * d->img->width(), - br_y * d->img->height(), - br_x * d->img->width(), - d->img->height()); + rect.setCoords(tl_x * img_width, + br_y * img_height, + br_x * img_width, + img_height); d->hideBottom->setRect(rect); // hide area - rect.setCoords(tl_x * d->img->width(), tl_y * d->img->height(), - br_x * d->img->width(), br_y * d->img->height()); + rect.setCoords(tl_x * img_width, tl_y * img_height, + br_x * img_width, br_y * img_height); d->hideArea->setRect(rect); - d->hideLeft->show(); - d->hideRight->show(); - d->hideTop->show(); - d->hideBottom->show(); + if (d->m_displaying_preview) { + // we don't display progress over image if whole area is scanned + d->hideLeft->show(); + d->hideRight->show(); + d->hideTop->show(); + d->hideBottom->show(); + } // the hide area is hidden until setHighlightShown is called. d->hideArea->hide(); } @@ -345,7 +368,9 @@ // ------------------------------------------------------------------------ void KSaneViewer::setHighlightShown(int percentage, QColor hideColor) { - if (percentage >= 100) { + //overlay progress shouldn't be shown for last image mode (!d->m_displaying_preview) + //as it always displays only whole scanned area (not a selected part of it) + if (percentage >= 100 || !d->m_displaying_preview) { d->hideArea->hide(); return; } @@ -366,7 +391,9 @@ // ------------------------------------------------------------------------ void KSaneViewer::updateHighlight() { - if (d->selection->isVisible()) { + if (d->m_displaying_preview && d->selection->isVisible()) { + // all selections operations are permitted for preview image only + QRectF rect; // Left rect.setCoords(0, 0, d->selection->rect().left(), d->img->height()); @@ -410,8 +437,7 @@ // ------------------------------------------------------------------------ void KSaneViewer::clearHighlight() { - d->hideLeft - ->hide(); + d->hideLeft->hide(); d->hideRight->hide(); d->hideTop->hide(); d->hideBottom->hide(); @@ -421,7 +447,8 @@ // ------------------------------------------------------------------------ void KSaneViewer::updateSelVisibility() { - if ((d->selection->rect().width() > 0.001) && + if (d->m_displaying_preview && // all selections operations are permitted for preview image only + (d->selection->rect().width() > 0.001) && (d->selection->rect().height() > 0.001) && ((d->img->width() - d->selection->rect().width() > 0.1) || (d->img->height() - d->selection->rect().height() > 0.1))) { @@ -453,17 +480,26 @@ return activeSelection(tl_x, tl_y, br_x, br_y); } - tl_x = d->selectionList[index]->rect().left() / d->img->width(); - tl_y = d->selectionList[index]->rect().top() / d->img->height(); - br_x = d->selectionList[index]->rect().right() / d->img->width(); - br_y = d->selectionList[index]->rect().bottom() / d->img->height(); + int img_width = d->preview_img_size.width(); + int img_height = d->preview_img_size.height(); + tl_x = d->selectionList[index]->rect().left() / img_width; + tl_y = d->selectionList[index]->rect().top() / img_height; + br_x = d->selectionList[index]->rect().right() / img_width; + br_y = d->selectionList[index]->rect().bottom() / img_height; return true; } // ------------------------------------------------------------------------ bool KSaneViewer::activeSelection(float &tl_x, float &tl_y, float &br_x, float &br_y) { - if (!d->selection->isVisible()) { + bool ret_default; + if (d->m_displaying_preview) { + ret_default = !d->selection->isVisible(); + } else { + ret_default = d->selection->rect().isEmpty(); + } + + if (ret_default) { tl_x = 0.0; tl_y = 0.0; br_x = 1.0; @@ -471,10 +507,13 @@ return true; } - tl_x = d->selection->rect().left() / d->img->width(); - tl_y = d->selection->rect().top() / d->img->height(); - br_x = d->selection->rect().right() / d->img->width(); - br_y = d->selection->rect().bottom() / d->img->height(); + // we use cached preview_img_size as viewer could be switched to last scan mode + int img_width = d->preview_img_size.width(); + int img_height = d->preview_img_size.height(); + tl_x = d->selection->rect().left() / img_width; + tl_y = d->selection->rect().top() / img_height; + br_x = d->selection->rect().right() / img_width; + br_y = d->selection->rect().bottom() / img_height; if ((tl_x == br_x) || (tl_y == br_y)) { tl_x = 0.0; @@ -514,6 +553,17 @@ updateSelVisibility(); } +// ------------------------------------------------------------------------ + +void KSaneViewer::setDisplayingPreview(bool is_preview) +{ + d->m_displaying_preview = is_preview; + updateSelVisibility(); + for (int i = 0; i < d->selectionList.count(); ++i) { + d->selectionList[i]->setVisible(is_preview); + } +} + // ------------------------------------------------------------------------ void KSaneViewer::wheelEvent(QWheelEvent *e) { @@ -531,7 +581,8 @@ // ------------------------------------------------------------------------ void KSaneViewer::mousePressEvent(QMouseEvent *e) { - if (e->button() == Qt::LeftButton) { + if (d->m_displaying_preview && e->button() == Qt::LeftButton) { + // selection edit isn't permitted if viewer displays last scan tab d->m_left_last_x = e->x(); d->m_left_last_y = e->y(); QPointF scenePoint = mapToScene(e->pos()); @@ -555,6 +606,12 @@ // ------------------------------------------------------------------------ void KSaneViewer::mouseReleaseEvent(QMouseEvent *e) { + if (!d->m_displaying_preview) { + // selection edit isn't permitted if viewer displays last scan tab + QGraphicsView::mouseReleaseEvent(e); + return; + } + bool removed = false; if (e->button() == Qt::LeftButton) { if ((d->selection->rect().width() < 0.001) || @@ -611,6 +668,12 @@ // ------------------------------------------------------------------------ void KSaneViewer::mouseMoveEvent(QMouseEvent *e) { + if (!d->m_displaying_preview) { + // selection edit isn't permitted if viewer displays last scan tab + QGraphicsView::mouseMoveEvent(e); + return; + } + QPointF scenePoint = mapToScene(e->pos()); if (e->buttons()&Qt::LeftButton) { diff --git a/src/ksanewidget.h b/src/ksanewidget.h --- a/src/ksanewidget.h +++ b/src/ksanewidget.h @@ -249,6 +249,14 @@ * @param hidden defines the state to set. */ void setScanButtonHidden(bool hidden); + /* This function is used to switch on display of the last scanned image. + * The default state is disabled. + * If it's on then a Tab is displayed that allows to switch between preview and new display modes. + * @param enable defines the state to set. */ + void enableDisplayLastScan(bool enable); + + bool displayLastScan(); + public Q_SLOTS: /** This method can be used to cancel a scan or prevent an automatic new scan. */ void scanCancel(); diff --git a/src/ksanewidget.cpp b/src/ksanewidget.cpp --- a/src/ksanewidget.cpp +++ b/src/ksanewidget.cpp @@ -197,13 +197,29 @@ d->m_warmingUp->setMinimumHeight(minHeight); d->m_previewFrame = new QWidget; - QVBoxLayout *preview_layout = new QVBoxLayout(d->m_previewFrame); + QHBoxLayout *preview_h_layout = new QHBoxLayout(d->m_previewFrame); + preview_h_layout->setContentsMargins(0, 0, 0, 0); + preview_h_layout->setSpacing(2); + QWidget* preview_holder = new QWidget(d->m_previewFrame); + preview_h_layout->addWidget(preview_holder, 100); + QVBoxLayout *preview_layout = new QVBoxLayout(preview_holder); preview_layout->setContentsMargins(0, 0, 0, 0); preview_layout->addWidget(d->m_previewViewer, 100); preview_layout->addWidget(d->m_warmingUp, 0); preview_layout->addWidget(d->m_activityFrame, 0); preview_layout->addWidget(d->m_btnFrame, 0); + // Create Tab Widget to switch between preview image display nd last scanned image display + d->m_viewsTabBar = new QTabBar(); + d->m_viewsTabBar->setShape(QTabBar::RoundedEast); + d->m_viewsTabBar->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); + d->m_viewsTabBar->setExpanding(false); + connect(d->m_viewsTabBar, SIGNAL(currentChanged(int)), d, SLOT(currentTabChanged(int))); + + d->m_viewsTabBar->addTab(i18n("Preview")); + d->m_viewsTabBar->addTab(i18n("Current Scan")); + preview_h_layout->addWidget(d->m_viewsTabBar, 0); + // Create Options Widget d->m_optsTabWidget = new QTabWidget(); @@ -486,7 +502,7 @@ connect(d->m_previewThread, SIGNAL(finished()), d, SLOT(previewScanDone())); // Create the read thread - d->m_scanThread = new KSaneScanThread(d->m_saneHandle, &d->m_scanData); + d->m_scanThread = new KSaneScanThread(d->m_saneHandle, &d->m_scanData, &d->m_scanImg); connect(d->m_scanThread, SIGNAL(finished()), d, SLOT(oneFinalScanDone())); // Create the options interface @@ -662,6 +678,10 @@ void KSaneWidget::startPreviewScan() { if (d->m_btnFrame->isEnabled()) { + if (d->isScanImageDisplayed()) { + // resets displayed image tab to preview mode if Preview button is pressed while displaying last scanned image + d->m_viewsTabBar->setCurrentIndex(0); + } d->startPreviewScan(); } else { // if the button frame is disabled, there is no open device to scan from @@ -918,4 +938,29 @@ d->m_scanBtn->setHidden(hidden); } +void KSaneWidget::enableDisplayLastScan(bool enable) +{ + d->m_displayLastScan = enable; + d->m_viewsTabBar->setVisible(enable); + + if (!d->m_scanOngoing) { + if (!enable) { + d->m_viewsTabBar->setCurrentIndex(0); + d->m_scanImg = QImage(); // free memory + } else { + d->m_scanImg = QImage(d->currentSelectionSize(), QImage::Format_RGB32); + d->m_scanImg.fill(0xFFFFFFFF); + if (d->isScanImageDisplayed()) { + d->m_previewViewer->setQImage(&d->m_scanImg, false, false); + d->m_previewViewer->zoom2Fit(); + } + } + } +} + +bool KSaneWidget::displayLastScan() +{ + return d->m_displayLastScan; +} + } // NameSpace KSaneIface diff --git a/src/ksanewidget_p.h b/src/ksanewidget_p.h --- a/src/ksanewidget_p.h +++ b/src/ksanewidget_p.h @@ -42,6 +42,7 @@ #include #include #include +#include #include #include "ksanewidget.h" @@ -74,6 +75,11 @@ KSaneOption *getOption(const QString &name); KSaneWidget::ImageFormat getImgFormat(SANE_Parameters ¶ms); int getBytesPerLines(SANE_Parameters ¶ms); + // returns true if display last scan feature is on and scan tab is displayed + bool isScanImageDisplayed(); + // returns QSize of current selection at preview image + // regardless of display mode (preview/last scan) + QSize currentSelectionSize(); public Q_SLOTS: void devListUpdated(); @@ -83,6 +89,7 @@ void previewScanDone(); void oneFinalScanDone(); void updateProgress(); + void updateAndZoom2Fit(); private Q_SLOTS: void scheduleValReload(); @@ -98,6 +105,7 @@ void invertPreview(); void pollPollOptions(); + void currentTabChanged(int idx); public: void alertUser(int type, const QString &strStatus); @@ -114,6 +122,7 @@ QSplitter *m_splitter; SplitterCollapser *m_optionsCollapser; + QTabBar *m_viewsTabBar; QWidget *m_previewFrame; KSaneViewer *m_previewViewer; QWidget *m_btnFrame; @@ -165,6 +174,8 @@ float m_previewDPI; QImage m_previewImg; bool m_isPreview; + QImage m_scanImg; // additional QImage to display last scanned image + bool m_displayLastScan; // defines if KSaneWidget's *display last scanned image* feature is enabled bool m_autoSelect; int m_selIndex; diff --git a/src/ksanewidget_p.cpp b/src/ksanewidget_p.cpp --- a/src/ksanewidget_p.cpp +++ b/src/ksanewidget_p.cpp @@ -68,6 +68,7 @@ // scanning variables m_isPreview = false; + m_displayLastScan = false; m_saneHandle = nullptr; m_previewThread = nullptr; @@ -719,7 +720,11 @@ m_previewImg.fill(0xFFFFFFFF); // set the new image - m_previewViewer->setQImage(&m_previewImg); + if (m_displayLastScan) { + updateAndZoom2Fit(); + } else { + m_previewViewer->setQImage(&m_previewImg); + } } void KSaneWidgetPrivate::startPreviewScan() @@ -883,6 +888,24 @@ return; } +QSize KSaneWidgetPrivate::currentSelectionSize() +{ + float x1 = 0, y1 = 0, x2 = 0, y2 = 0, max_x, max_y; + if ((m_optTlX != nullptr) && (m_optTlY != nullptr) && (m_optBrX != nullptr) && (m_optBrY != nullptr)) { + // get maximums + m_optBrX->getMaxValue(max_x); + m_optBrY->getMaxValue(max_y); + + // read the selection from the viewer + m_previewViewer->selectionAt(m_selIndex, x1, y1, x2, y2); + + // calculate the option values + x1 *= max_x; y1 *= max_y; + x2 *= max_x; y2 *= max_y; + } + return QSize(x2-x1, y2-y1); +} + void KSaneWidgetPrivate::startFinalScan() { if (m_scanOngoing) { @@ -895,6 +918,11 @@ m_selIndex = 0; + if (m_displayLastScan) { + m_scanImg = QImage(); + updateAndZoom2Fit(); + } + if ((m_optTlX != nullptr) && (m_optTlY != nullptr) && (m_optBrX != nullptr) && (m_optBrY != nullptr)) { // get maximums m_optBrX->getMaxValue(max_x); @@ -925,6 +953,7 @@ setBusy(true); m_updProgressTmr.start(); m_scanThread->setImageInverted(m_invertColors->isChecked()); + m_scanThread->setDisplayLastScan(m_displayLastScan); m_scanThread->start(); } @@ -933,6 +962,11 @@ m_updProgressTmr.stop(); updateProgress(); + if (!m_displayLastScan && !m_scanImg.isNull()) { + // free m_scanImg memory in case setting was switched off while m_scanOngoing + m_scanImg = QImage(); + } + if (m_closeDevicePending) { setBusy(false); sane_close(m_saneHandle); @@ -966,6 +1000,7 @@ // in batch mode only one area can be scanned per page //qDebug() << "source == " << source; m_updProgressTmr.start(); + m_scanThread->setDisplayLastScan(m_displayLastScan); m_scanThread->start(); return; } @@ -982,6 +1017,7 @@ // in batch mode only one area can be scanned per page //qDebug() << "source == \"Automatic Document Feeder\""; m_updProgressTmr.start(); + m_scanThread->setDisplayLastScan(m_displayLastScan); m_scanThread->start(); return; } @@ -1023,6 +1059,7 @@ valReload(); } m_updProgressTmr.start(); + m_scanThread->setDisplayLastScan(m_displayLastScan); m_scanThread->start(); return; } @@ -1138,9 +1175,21 @@ } } } else { - if (!m_progressBar->isVisible() && (m_scanThread->saneStartDone())) { - m_warmingUp->hide(); - m_activityFrame->show(); + if (m_scanThread->saneStartDone()) { + if (!m_progressBar->isVisible() || m_scanThread->imageResized()) { + m_warmingUp->hide(); + m_activityFrame->show(); + if (isScanImageDisplayed()) { + m_scanThread->imgMutex.lock(); + m_previewViewer->setQImage(&m_scanImg, false, false); + m_previewViewer->zoom2Fit(); + m_scanThread->imgMutex.unlock(); + } + } else if (isScanImageDisplayed()) { + m_scanThread->imgMutex.lock(); + m_previewViewer->updateImage(); + m_scanThread->imgMutex.unlock(); + } } progress = m_scanThread->scanProgress(); m_previewViewer->setHighlightShown(progress); @@ -1173,4 +1222,34 @@ } } +void KSaneWidgetPrivate::currentTabChanged(int /*idx*/) +{ + if (!isScanImageDisplayed()) { + // show preview image + m_previewViewer->setQImage(&m_previewImg, true, false); + m_previewViewer->zoom2Fit(); + } else { + // show current scan + if (m_scanImg.isNull()) { + m_scanImg = QImage(currentSelectionSize(), QImage::Format_RGB32); + m_scanImg.fill(0xFFFFFFFF); + } + m_previewViewer->setQImage(&m_scanImg, false, false); + m_previewViewer->zoom2Fit(); + } + + // must be called after new image is set + m_previewViewer->setDisplayingPreview(!isScanImageDisplayed()); +} + +void KSaneWidgetPrivate::updateAndZoom2Fit() +{ + currentTabChanged(m_viewsTabBar->currentIndex()); +} + +bool KSaneWidgetPrivate::isScanImageDisplayed() +{ + return m_viewsTabBar->currentIndex() != 0; +} + } // NameSpace KSaneIface