diff --git a/src/KSaneImageSaver.h b/src/KSaneImageSaver.h --- a/src/KSaneImageSaver.h +++ b/src/KSaneImageSaver.h @@ -34,26 +34,30 @@ { Q_OBJECT public: - explicit KSaneImageSaver(QObject *parent = 0); + explicit KSaneImageSaver(QObject *parent = nullptr); + explicit KSaneImageSaver(const QString &name, const QByteArray &data, const QImage &img, int width, int height, int bpl, int dpi, int format, const QString& fileFormat, int quality = -1, QObject *parent = nullptr); ~KSaneImageSaver(); - bool savePng(const QString &name, const QByteArray &data, int width, int height, int format, int dpi); + void setSettings(const QString &name, const QByteArray &data, const QImage &img, int width, int height, int bpl, int dpi, int format, const QString& fileFormat, int quality = -1); - bool savePngSync(const QString &name, const QByteArray &data, int width, int height, int format, int dpi); + bool savePngAsync(); - bool saveTiff(const QString &name, const QByteArray &data, int width, int height, int format); + bool savePngSync(); - bool saveTiffSync(const QString &name, const QByteArray &data, int width, int height, int format); + bool saveTiffAsync(); + + bool saveTiffSync(); Q_SIGNALS: void imageSaved(bool success); + void userMessage(int type, const QString &strStatus); protected: void run() Q_DECL_OVERRIDE; private: - struct Private; - Private *const d; + class Private; + Private * d; }; diff --git a/src/KSaneImageSaver.cpp b/src/KSaneImageSaver.cpp --- a/src/KSaneImageSaver.cpp +++ b/src/KSaneImageSaver.cpp @@ -30,62 +30,102 @@ #include #include +#include -struct KSaneImageSaver::Private { +class KSaneImageSaver::Private { +public: enum ImageType { ImageTypePNG, ImageTypeTIFF }; + Private(KSaneImageSaver * parent = nullptr); + Private(const QString &name, const QByteArray &data, const QImage &img, int width, int height, int bpl, int dpi, int format, const QString &fileFormat, int quality = -1, KSaneImageSaver * parent = nullptr); + bool m_savedOk; QMutex m_runMutex; - KSaneImageSaver *q; QString m_name; QByteArray m_data; + QImage m_img; int m_width; int m_height; - int m_format; + int m_bpl; int m_dpi; + int m_format; + QString m_fileFormat; + int m_quality; ImageType m_type; + KSaneImageSaver *q; + bool savePng(); bool saveTiff(); + QImage toQImage(const QByteArray &data, + int width, + int height, + int bytes_per_line, + int dpi, + KSaneIface::KSaneWidget::ImageFormat format); + QImage toQImageSilent(const QByteArray &data, + int width, + int height, + int bytes_per_line, + int dpi, + KSaneIface::KSaneWidget::ImageFormat format); }; // ------------------------------------------------------------------------ -KSaneImageSaver::KSaneImageSaver(QObject *parent) : QThread(parent), d(new Private) +KSaneImageSaver::Private::Private(const QString &name, const QByteArray &data, const QImage &img, int width, int height, int bpl, int dpi, int format, const QString& fileFormat, int quality, KSaneImageSaver * parent) : + m_name(name), m_data(data), m_img(img), m_width(width), m_height(height), m_bpl(bpl), m_dpi(dpi), m_format(format), m_fileFormat(fileFormat), m_quality(quality), m_type(Private::ImageTypePNG), q(parent) { - d->q = this; } +// ------------------------------------------------------------------------ +KSaneImageSaver::Private::Private(KSaneImageSaver * parent): q(parent) +{ +} + +// ------------------------------------------------------------------------ +KSaneImageSaver::KSaneImageSaver(QObject *parent) : QThread(parent), d(new Private(this)) +{ +} + +// ------------------------------------------------------------------------ +KSaneImageSaver::KSaneImageSaver(const QString &name, const QByteArray &data, const QImage &img, int width, int height, int bpl, int dpi, int format, const QString& fileFormat, int quality, QObject *parent) : + QThread(parent), d(new Private(name, data, img, width, height, bpl, dpi, format, fileFormat, quality, this)) +{ +} + +// ------------------------------------------------------------------------ +void KSaneImageSaver::setSettings(const QString &name, const QByteArray &data, const QImage &img, int width, int height, int bpl, int dpi, int format, const QString& fileFormat, int quality) +{ + if (d) { + delete d; + } + d = new Private(name, data, img, width, height, bpl, dpi, format, fileFormat, quality, this); +} // ------------------------------------------------------------------------ KSaneImageSaver::~KSaneImageSaver() { delete d; } -bool KSaneImageSaver::savePng(const QString &name, const QByteArray &data, int width, int height, int format, int dpi) +bool KSaneImageSaver::savePngAsync() { if (!d->m_runMutex.tryLock()) { return false; } - d->m_name = name; - d->m_data = data; - d->m_width = width; - d->m_height = height; - d->m_format = format; - d->m_dpi = dpi; d->m_type = Private::ImageTypePNG; start(); return true; } -bool KSaneImageSaver::savePngSync(const QString &name, const QByteArray &data, int width, int height, int format, int dpi) +bool KSaneImageSaver::savePngSync() { - if (!savePng(name, data, width, height, format, dpi)) { + if (!savePngAsync()) { qDebug() << "fail"; return false; } @@ -93,17 +133,12 @@ return d->m_savedOk; } -bool KSaneImageSaver::saveTiff(const QString &name, const QByteArray &data, int width, int height, int format) +bool KSaneImageSaver::saveTiffAsync() { if (!d->m_runMutex.tryLock()) { return false; } - d->m_name = name; - d->m_data = data; - d->m_width = width; - d->m_height = height; - d->m_format = format; d->m_type = Private::ImageTypeTIFF; qDebug() << "saving Tiff images is not yet supported"; @@ -111,9 +146,9 @@ return false; } -bool KSaneImageSaver::saveTiffSync(const QString &name, const QByteArray &data, int width, int height, int format) +bool KSaneImageSaver::saveTiffSync() { - if (!saveTiff(name, data, width, height, format)) { + if (!saveTiffAsync()) { return false; } wait(); @@ -122,15 +157,30 @@ void KSaneImageSaver::run() { - if (d->m_type == Private::ImageTypeTIFF) { - d->m_savedOk = d->saveTiff(); - emit imageSaved(d->m_savedOk); - } - else { - d->m_savedOk = d->savePng(); - emit imageSaved(d->m_savedOk); + if ((d->m_format == KSaneIface::KSaneWidget::FormatRGB_16_C) || + (d->m_format == KSaneIface::KSaneWidget::FormatGrayScale16)) { + + if (d->m_type == Private::ImageTypeTIFF) { + d->m_savedOk = d->saveTiff(); + emit imageSaved(d->m_savedOk); + } + else { + d->m_savedOk = d->savePng(); + emit imageSaved(d->m_savedOk); + } + d->m_runMutex.unlock(); + } else { + if (d->m_img.width() < 1) { + d->m_img = d->toQImage(d->m_data, d->m_width, d->m_height, d->m_bpl, d->m_dpi, (KSaneIface::KSaneWidget::ImageFormat)d->m_format); + } + if (d->m_img.save(d->m_name, d->m_fileFormat.toStdString().c_str(), d->m_quality)) { + //m_showImgDialog->close(); // calling close() on a closed window does nothing. + } + else { + //perrorMessageBox(i18n("Failed to save image")); + return; + } } - d->m_runMutex.unlock(); } bool KSaneImageSaver::Private::saveTiff() @@ -246,3 +296,103 @@ return true; } +QImage KSaneImageSaver::Private::toQImageSilent(const QByteArray &data, + int width, + int height, + int bytes_per_line, + int dpi, + KSaneIface::KSaneWidget::ImageFormat format) +{ + QImage img; + int j = 0; + QVector table; + QRgb *imgLine; + + switch (format) { + case KSaneIface::KSaneWidget::FormatBlackWhite: + img = QImage((uchar *)data.data(), + width, + height, + bytes_per_line, + QImage::Format_Mono); + // The color table must be set + table.append(0xFFFFFFFF); + table.append(0xFF000000); + img.setColorTable(table); + break; + + case KSaneIface::KSaneWidget::FormatGrayScale8: { + img = QImage(width, height, QImage::Format_RGB32); + int dI = 0; + for (int i = 0; (i < img.height() && dI < data.size()); i++) { + imgLine = reinterpret_cast(img.scanLine(i)); + for (j = 0; (j < img.width() && dI < data.size()); j++) { + imgLine[j] = qRgb(data[dI], data[dI], data[dI]); + dI++; + } + } + break; + } + case KSaneIface::KSaneWidget::FormatGrayScale16: { + img = QImage(width, height, QImage::Format_RGB32); + int dI = 1; + for (int i = 0; (i < img.height() && dI < data.size()); i++) { + imgLine = reinterpret_cast(img.scanLine(i)); + for (j = 0; (j < img.width() && dI < data.size()); j++) { + imgLine[j] = qRgb(data[dI], data[dI], data[dI]); + dI += 2; + } + } + break; + } + case KSaneIface::KSaneWidget::FormatRGB_8_C: { + img = QImage(width, height, QImage::Format_RGB32); + int dI = 0; + for (int i = 0; (i < img.height() && dI < data.size()); i++) { + imgLine = reinterpret_cast(img.scanLine(i)); + for (j = 0; (j < img.width() && dI < data.size()); j++) { + imgLine[j] = qRgb(data[dI], data[dI + 1], data[dI + 2]); + dI += 3; + } + } + break; + } + case KSaneIface::KSaneWidget::FormatRGB_16_C: { + img = QImage(width, height, QImage::Format_RGB32); + int dI = 1; + for (int i = 0; (i < img.height() && dI < data.size()); i++) { + imgLine = reinterpret_cast(img.scanLine(i)); + for (j = 0; (j < img.width() && dI < data.size()); j++) { + imgLine[j] = qRgb(data[dI], data[dI + 2], data[dI + 4]); + dI += 6; + } + } + break; + } + case KSaneIface::KSaneWidget::FormatNone: + default: + qDebug() << "Unsupported conversion"; + break; + } + float dpm = dpi * (1000.0 / 25.4); + img.setDotsPerMeterX(dpm); + img.setDotsPerMeterY(dpm); + return img; +} + +QImage KSaneImageSaver::Private::toQImage(const QByteArray &data, + int width, + int height, + int bytes_per_line, + int dpi, + KSaneIface::KSaneWidget::ImageFormat format) +{ + + if ((format == KSaneIface::KSaneWidget::FormatRGB_16_C) || (format == KSaneIface::KSaneWidget::FormatGrayScale16)) { + emit q->userMessage(KSaneIface::KSaneWidget::ErrorGeneral, + i18n("The image data contained 16 bits per color, " + "but the color depth has been truncated to 8 bits per color.")); + } + + return toQImageSilent(data, width, height, bytes_per_line, dpi, format); +} diff --git a/src/skanlite.cpp b/src/skanlite.cpp --- a/src/skanlite.cpp +++ b/src/skanlite.cpp @@ -372,18 +372,39 @@ } } +bool pathExists(const QString& dir, QWidget* parent) +{ + // propose directory creation if doesn't exists + QUrl dirUrl(dir); + if (dirUrl.isLocalFile()) { + QDir path(dirUrl.toLocalFile()); + if (!path.exists()) { + if (KMessageBox::questionYesNo(parent, i18n("Directory doesn't exist, do you wish to create it?")) == KMessageBox::ButtonCode::Yes ) { + if (!path.mkpath(QLatin1String("."))) { + KMessageBox::error(parent, i18n("Could not create directory %1", path.path())); + return false; + } + } + } + } + return true; +} + void Skanlite::saveImage() { // ask the first time if we are in "ask on first" mode - if ((m_settingsUi.saveModeCB->currentIndex() == SaveModeAskFirst) && m_firstImage) { + QString dir = QDir::cleanPath(m_saveLocation->u_urlRequester->url().url()).append(QLatin1Char('/')); //make sure whole value is processed as path to directory + + while ((m_firstImage && (m_settingsUi.saveModeCB->currentIndex() == SaveModeAskFirst)) || + !pathExists(dir, this)) { if (m_saveLocation->exec() != QFileDialog::Accepted) { m_ksanew->scanCancel(); // In case we are cancelling a document feeder scan return; } + dir = QDir::cleanPath(m_saveLocation->u_urlRequester->url().url()).append(QLatin1Char('/')); m_firstImage = false; } - QString dir = QDir::cleanPath(m_saveLocation->u_urlRequester->url().url()).append(QLatin1Char('/')); //make sure whole value is processed as path to directory QString prefix = m_saveLocation->u_imgPrefix->text(); QString imgFormat = m_saveLocation->u_imgFormat->currentText().toLower(); int fileNumber = m_saveLocation->u_numStartFrom->value(); @@ -487,9 +508,9 @@ QString localName; QString suffix = QFileInfo(fileUrl.fileName()).suffix(); - const char *fileFormat = nullptr; + QString fileFormat; if (suffix.isEmpty()) { - fileFormat = "png"; + fileFormat = QLatin1String("png"); } if (!fileUrl.isLocalFile()) { @@ -508,70 +529,65 @@ } // Save - if ((m_format == KSaneIface::KSaneWidget::FormatRGB_16_C) || - (m_format == KSaneIface::KSaneWidget::FormatGrayScale16)) { - KSaneImageSaver saver; - if (saver.savePngSync(localName, m_data, m_width, m_height, m_format, m_ksanew->currentDPI())) { - m_showImgDialog->close(); // closing the window if it is closed should not be a problem. - } - else { - perrorMessageBox(i18n("Failed to save image")); - return; - } - } - else { - // create the image if needed. - if (m_img.width() < 1) { - m_img = m_ksanew->toQImage(m_data, m_width, m_height, m_bytesPerLine, (KSaneIface::KSaneWidget::ImageFormat)m_format); + + KSaneImageSaver *saver = new KSaneImageSaver(localName, m_data, m_img, m_width, m_height, m_bytesPerLine, m_ksanew->currentDPI(), m_format, fileFormat, quality); + connect(saver, &KSaneImageSaver::userMessage, this, &Skanlite::alertUser); + connect(saver, &KSaneImageSaver::userMessage, &m_dbusInterface, &DBusInterface::userMessage); + connect(saver, &KSaneImageSaver::imageSaved, [=](bool success) mutable { //mutable as fileNumber may be adjusted + + delete saver; + + if (!success) { + perrorMessageBox(i18n("Failed to save image")); + return; } - if (m_img.save(localName, fileFormat, quality)) { - m_showImgDialog->close(); // calling close() on a closed window does nothing. + + m_showImgDialog->close(); // calling close() on a closed window does nothing. + + + if (!fileUrl.isLocalFile()) { + QFile tmpFile(localName); + tmpFile.open(QIODevice::ReadOnly); + auto uploadJob = KIO::storedPut(&tmpFile, fileUrl, -1); + KJobWidgets::setWindow(uploadJob, QApplication::activeWindow()); + bool ok = uploadJob->exec(); + tmpFile.close(); + tmpFile.remove(); + if (!ok) { + KMessageBox::sorry(nullptr, i18n("Failed to upload image")); + } + else { + emit m_dbusInterface.imageSaved(fileUrl.toString()); + } } else { - perrorMessageBox(i18n("Failed to save image")); - return; + emit m_dbusInterface.imageSaved(localName); } - } - if (!fileUrl.isLocalFile()) { - QFile tmpFile(localName); - tmpFile.open(QIODevice::ReadOnly); - auto uploadJob = KIO::storedPut(&tmpFile, fileUrl, -1); - KJobWidgets::setWindow(uploadJob, QApplication::activeWindow()); - bool ok = uploadJob->exec(); - tmpFile.close(); - tmpFile.remove(); - if (!ok) { - KMessageBox::sorry(0, i18n("Failed to upload image")); + // Save the file base name without number + QString baseName = QFileInfo(fileUrl.fileName()).completeBaseName(); + while ((!baseName.isEmpty()) && (baseName[baseName.size() - 1].isNumber())) { + baseName.remove(baseName.size() - 1, 1); } - else { - emit m_dbusInterface.imageSaved(fileUrl.toString()); + m_saveLocation->u_imgPrefix->setText(baseName); + + // Save the number + QString fileNumStr = QFileInfo(fileUrl.fileName()).completeBaseName(); + fileNumStr.remove(baseName); + fileNumber = fileNumStr.toInt(); + if (fileNumber) { + m_saveLocation->u_numStartFrom->setValue(fileNumber + 1); } - } - else { - emit m_dbusInterface.imageSaved(localName); - } - // Save the file base name without number - QString baseName = QFileInfo(fileUrl.fileName()).completeBaseName(); - while ((!baseName.isEmpty()) && (baseName[baseName.size() - 1].isNumber())) { - baseName.remove(baseName.size() - 1, 1); - } - m_saveLocation->u_imgPrefix->setText(baseName); + if (m_settingsUi.saveModeCB->currentIndex() == SaveModeManual) { + // Save last used dir, prefix and suffix. + m_saveLocation->u_urlRequester->setUrl(KIO::upUrl(fileUrl)); + m_saveLocation->u_imgFormat->setCurrentText(QFileInfo(fileUrl.fileName()).suffix()); + } + }); - // Save the number - QString fileNumStr = QFileInfo(fileUrl.fileName()).completeBaseName(); - fileNumStr.remove(baseName); - fileNumber = fileNumStr.toInt(); - if (fileNumber) { - m_saveLocation->u_numStartFrom->setValue(fileNumber + 1); - } - if (m_settingsUi.saveModeCB->currentIndex() == SaveModeManual) { - // Save last used dir, prefix and suffix. - m_saveLocation->u_urlRequester->setUrl(KIO::upUrl(fileUrl)); - m_saveLocation->u_imgFormat->setCurrentText(QFileInfo(fileUrl.fileName()).suffix()); - } + saver->savePngAsync(); } void Skanlite::getDir(void)