Очередное исправление экспорта разделённых сканов. Внесены некоторые улучшения:
- При нажатии на кнопку "Export" в окне появляется надпись "Starting the export...", а уже после неё отображается в реальном времени постраничная индикация экспорта. Я никак не мог ранее этого добиться - но всё-таки смог с помощью Tulon.
- Проверка открыт ли проект и нет ли пакетной обработки перенесена на начало обработчика нажатия кнопки "Export" - с окончания. Это сделано для того, чтобы не вылезала надпись "Starting the export..." если, допустим, проект не открыт.
- Добавил возможность прервать экспорт в процессе его совершения. После старта экспорта кнопка "Export" меняет своё название на "Stop" - и её можно нажать, чтобы остановить процесс. Правда, кнопка получилась слегка "жестковата" - т.е. не сразу реагирует на нажатие.
- Добавил дату сборки в качестве "версии" программы.
- Попытался русифицировать свои добавления, но пока не слишком успешно. Удалось русифицировать пока лишь визуальные элементы.
- Убрал баг: ранее, если экспортировался чёрно-белый скан, установленный в режиме "Mixed", то для него создавался сплошной белый задний субскан. Теперь не создаётся.
- Кстати, галки "Split mixed" и "Default output folder" авто-запоминаются между сеансами запуска программы. По-видимому, в реестре Windows - больше негде. Точно не знаю, потому что это абстрагируется классом QSettings.
Коды исправления:
C:\build\scantailor-0.9.11.1\ExportDialog.h
#ifndef EXPORT_DIALOG_H_
#define EXPORT_DIALOG_H_
#include "ui_ExportDialog.h"
#include <QDialog>
class ExportDialog : public QDialog
{
Q_OBJECT
public:
ExportDialog(QWidget* parent = 0);
virtual ~ExportDialog();
void setCount(int count);
void StepProgress();
void reset(void);
void setExportLabel(void);
void setStartExport(void);
signals:
void ExportOutputSignal(QString export_dir_path, bool default_out_dir, bool split_subscans);
void ExportStopSignal();
void SetStartExportSignal();
public slots:
void startExport(void);
private slots:
void OnCheckSplitMixed(bool);
void OnCheckDefaultOutputFolder(bool);
void OnClickExport();
void outExportDirBrowse();
void outExportDirEdited(QString const&);
private:
Ui::ExportDialog ui;
bool m_autoOutDir;
void setExportOutputDir(QString const& dir);
int m_count;
};
#endif
#include "ExportDialog.h"
#include "ExportDialog.h.moc"
#include "OpenGLSupport.h"
#include "config.h"
#include <QSettings>
#include <QVariant>
#include <QDir>
#include <QFileDialog>
#include <QMessageBox>
#include <QTimer>
ExportDialog::ExportDialog(QWidget* parent)
: QDialog(parent)
{
ui.setupUi(this);
QSettings settings;
connect(ui.SplitMixed, SIGNAL(toggled(bool)), this, SLOT(OnCheckSplitMixed(bool)));
connect(ui.DefaultOutputFolder, SIGNAL(toggled(bool)), this, SLOT(OnCheckDefaultOutputFolder(bool)));
connect(ui.ExportButton, SIGNAL(clicked()), this, SLOT(OnClickExport()));
connect(ui.outExportDirBrowseBtn, SIGNAL(clicked()), this, SLOT(outExportDirBrowse()));
connect(ui.outExportDirLine, SIGNAL(textEdited(QString const&)),
this, SLOT(outExportDirEdited(QString const&))
);
ui.SplitMixed->setChecked(settings.value("settings/split_mixed").toBool());
ui.DefaultOutputFolder->setChecked(settings.value("settings/default_output_folder").toBool());
ui.labelFilesProcessed->clear();
ui.ExportButton->setText(tr("Export"));
}
ExportDialog::~ExportDialog()
{
}
void
ExportDialog::OnCheckSplitMixed(bool state)
{
QSettings settings;
settings.setValue("settings/split_mixed", state);
}
void
ExportDialog::OnCheckDefaultOutputFolder(bool state)
{
QSettings settings;
settings.setValue("settings/default_output_folder", state);
}
void
ExportDialog::OnClickExport()
{
if (ui.outExportDirLine->text().isEmpty() &&
!ui.DefaultOutputFolder->isChecked())
{
QMessageBox::warning(
this, tr("Error"),
tr("The export output directory is empty.")
);
return;
}
QDir const out_dir(ui.outExportDirLine->text());
if (out_dir.isAbsolute() && !out_dir.exists()) {
// Maybe create it.
bool create = m_autoOutDir;
if (!m_autoOutDir) {
create = QMessageBox::question(
this, tr("Create Directory?"),
tr("The export output directory doesn't exist. Create it?"),
QMessageBox::Yes|QMessageBox::No
) == QMessageBox::Yes;
if (!create) {
return;
}
}
if (create) {
if (!out_dir.mkpath(out_dir.path())) {
QMessageBox::warning(
this, tr("Error"),
tr("Unable to create the export output directory.")
);
return;
}
}
}
if ((!out_dir.isAbsolute() || !out_dir.exists())&& !ui.DefaultOutputFolder->isChecked()) {
QMessageBox::warning(
this, tr("Error"),
tr("The export output directory is not set or doesn't exist.")
);
return;
}
QString export_dir_path = ui.outExportDirLine->text();
bool split_subscans = ui.SplitMixed->isChecked();
bool default_out_dir = ui.DefaultOutputFolder->isChecked();
if (ui.ExportButton->text() != tr("Stop"))
emit SetStartExportSignal();
else
emit ExportStopSignal();
}
void
ExportDialog::outExportDirBrowse()
{
QString initial_dir(ui.outExportDirLine->text());
if (initial_dir.isEmpty() || !QDir(initial_dir).exists()) {
initial_dir = QDir::home().absolutePath();
}
QString const dir(
QFileDialog::getExistingDirectory(
this, tr("Export output directory"), initial_dir
)
);
if (!dir.isEmpty()) {
setExportOutputDir(dir);
}
}
void
ExportDialog::setExportOutputDir(QString const& dir)
{
ui.outExportDirLine->setText(QDir::toNativeSeparators(dir));
}
void
ExportDialog::outExportDirEdited(QString const& text)
{
m_autoOutDir = false;
}
void
ExportDialog::setCount(int count)
{
m_count = count;
ui.progressBar->setMaximum(m_count);
}
void
ExportDialog::StepProgress()
{
ui.labelFilesProcessed->setText(tr("Processed file") + " " + QString::number(ui.progressBar->value()+1) + " " + tr("of") + " " + QString::number(m_count));
ui.progressBar->setValue(ui.progressBar->value() + 1);
}
void
ExportDialog::startExport(void)
{
QString export_dir_path = ui.outExportDirLine->text();
bool split_subscans = ui.SplitMixed->isChecked();
bool default_out_dir = ui.DefaultOutputFolder->isChecked();
emit ExportOutputSignal(export_dir_path, default_out_dir, split_subscans);
}
void
ExportDialog::reset(void)
{
ui.labelFilesProcessed->clear();
ui.progressBar->setValue(0);
ui.ExportButton->setText(tr("Export"));
}
void
ExportDialog::setExportLabel(void)
{
ui.ExportButton->setText(tr("Export"));
}
void
ExportDialog::setStartExport(void)
{
m_count = 0;
ui.progressBar->setValue(0);
ui.labelFilesProcessed->setText(tr("Starting the export..."));
ui.ExportButton->setText(tr("Stop"));
QTimer::singleShot(1, this, SLOT(startExport()));
}
C:\build\scantailor-0.9.11.1\MainWindow.h
// begin of added by monday2000
#include <QMessageBox>
#include "stdint.h"
#include "TiffWriter.h"
#include "ImageLoader.h"
#include "ExportDialog.h"
// end of added by monday2000
class MainWindow :
......
public slots:
.......
//begin of added by monday2000
void ExportOutput(QString export_dir_path, bool default_out_dir, bool split_subscans);
void ExportStop();
void SetStartExport();
//end of added by monday2000
........
private slots:
..........
//begin of added by monday2000
void openExportDialog();
//end of added by monday2000
private:
//begin of added by monday2000
ExportDialog* m_p_export_dialog;
QVector<QString> m_outpaths_vector;
int ExportNextFile();
int m_exportTimerId;
QString m_export_dir;
bool m_split_subscans;
int m_pos_export;
//end of added by monday2000
}
C:\build\scantailor-0.9.11.1\MainWindow.cpp
MainWindow::MainWindow()
.......
//begin of added by monday2000
m_outpaths_vector(0),
//end of added by monday2000
......
connect(
actionSettings, SIGNAL(triggered(bool)),
this, SLOT(openSettingsDialog())
);
// begin of added by monday2000
connect(
actionExport, SIGNAL(triggered(bool)),
this, SLOT(openExportDialog())
);
// end of added by monday2000
..............
void
MainWindow::timerEvent(QTimerEvent* const event)
{
// begin of added by monday2000
if (event->timerId() == m_exportTimerId)
{
int res = ExportNextFile();
if (res)
{
killTimer(m_exportTimerId);
m_exportTimerId = 0;
if (res == 1)
{
m_p_export_dialog->setExportLabel();
QMessageBox::information(0, "Information", tr("The files export is finished."));
}
}
}
else
{
// end of added by monday2000
// We only use the timer event for delayed closing of the window.
killTimer(event->timerId());
if (closeProjectInteractive()) {
m_closing = true;
QSettings settings;
settings.setValue("mainWindow/maximized", isMaximized());
if (!isMaximized()) {
settings.setValue(
"mainWindow/nonMaximizedGeometry", saveGeometry()
);
}
close();
}
}//added by monday2000
}
.........
// begin of added by monday2000
void
MainWindow::openExportDialog()
{
m_p_export_dialog = new ExportDialog(this);
m_p_export_dialog->setAttribute(Qt::WA_DeleteOnClose);
m_p_export_dialog->setWindowModality(Qt::WindowModal);
m_p_export_dialog->show();
connect(m_p_export_dialog, SIGNAL(ExportOutputSignal(QString, bool, bool)), this, SLOT(ExportOutput(QString, bool, bool)));
connect(m_p_export_dialog, SIGNAL(ExportStopSignal()), this, SLOT(ExportStop()));
connect(m_p_export_dialog, SIGNAL(SetStartExportSignal()), this, SLOT(SetStartExport()));
}
template<typename MixedPixel>
bool GenerateSubscans(QImage& source_img, QImage& subscan1, QImage& subscan2, bool& non_bw_detected)
{
int const width = source_img.width();
int const height = source_img.height();
MixedPixel* source_line = reinterpret_cast<MixedPixel*>(source_img.bits());
int const source_stride = source_img.bytesPerLine() / sizeof(MixedPixel);
subscan1 = QImage(source_img.width(),source_img.height(),QImage::Format_Mono);
subscan2 = QImage(source_img.width(),source_img.height(),source_img.format());
subscan1.setDotsPerMeterX(source_img.dotsPerMeterX());
subscan1.setDotsPerMeterY(source_img.dotsPerMeterY());
subscan2.setDotsPerMeterX(source_img.dotsPerMeterX());
subscan2.setDotsPerMeterY(source_img.dotsPerMeterY());
QVector<QRgb> bw_palette(2);
bw_palette[0] = qRgb(0, 0, 0);
bw_palette[1] = qRgb(255, 255, 255);
subscan1.setColorTable(bw_palette);
if (subscan2.format() == QImage::Format_Indexed8)
{ //createGrayscalePalette() from C:\build\scantailor-0.9.11.1\imageproc\Grayscale.cpp
QVector<QRgb> palette(256);
for (int i = 0; i < 256; ++i) palette[i] = qRgb(i, i, i);
subscan2.setColorTable(palette);
}
uchar* subscan1_line = subscan1.bits();
int const subscan1_stride = subscan1.bytesPerLine();
MixedPixel* subscan2_line = reinterpret_cast<MixedPixel*>(subscan2.bits());
int const subscan2_stride = subscan2.bytesPerLine() / sizeof(MixedPixel);
uint32_t tmp_white_pixel = 0xffffffff;
uint32_t mask_pixel = 0x00ffffff;
MixedPixel mask = static_cast<MixedPixel>(mask_pixel);
bool mixed_detected = false;
for (int y = 0; y < height; ++y)
{
for (int x = 0; x < width; ++x)
{ //this line of code was suggested by Tulon:
if ((source_line[x] & mask) == 0 || (source_line[x] & mask) == mask) // BW
{
uint8_t value1 = static_cast<uint8_t>(source_line[x]);
//BW SetPixel from http://djvu-soft.narod.ru/bookscanlib/023.htm
value1 ? subscan1_line[x >> 3] |= (0x80 >> (x & 0x7)) : subscan1_line[x >> 3] &= (0xFF7F >> (x & 0x7));
subscan2_line[x] = static_cast<MixedPixel>(tmp_white_pixel);
mixed_detected = true;
}
else // non-BW
{
uint8_t value = static_cast<uint8_t>(tmp_white_pixel);
//BW SetPixel from http://djvu-soft.narod.ru/bookscanlib/023.htm
value ? subscan1_line[x >> 3] |= (0x80 >> (x & 0x7)) : subscan1_line[x >> 3] &= (0xFF7F >> (x & 0x7));
subscan2_line[x] = source_line[x];
non_bw_detected = true;
}
}
source_line += source_stride;
subscan1_line += subscan1_stride;
subscan2_line += subscan2_stride;
}
return mixed_detected;
}
int
MainWindow::ExportNextFile()
{
if (m_pos_export == m_outpaths_vector.size())
return 1; //all the files are processed
QImage subscan1;
QImage subscan2;
QString text_dir = m_export_dir + "\\1"; //folder for foreground subscans
QString pic_dir = m_export_dir + "\\2"; //folder for background subscans
QString out_file_path = m_outpaths_vector[m_pos_export];
QString st_num = QString::number(m_pos_export+1);
QString name;
for(int j=0;j<4-st_num.length();j++) name += "0";
name += st_num;
QString out_file_path1 = text_dir + "\\" + name + ".tif";
QString out_file_path2 = pic_dir + "\\" + name + ".tif";
QString out_file_path_no_split = m_export_dir + "\\" + name + ".tif";
if (!QFile().exists(out_file_path))
{
QMessageBox::critical(0, "Error", tr("The file") + " \"" + out_file_path + "\" " + tr("is not found") + ".");
return -1;
}
QImage out_img = ImageLoader::load(out_file_path);
if (m_split_subscans)
{
bool mixed_detected;
bool non_bw_detected = false;
bool non_bw;
if (out_img.format() == QImage::Format_Indexed8)
{
mixed_detected = GenerateSubscans<uint8_t>(out_img, subscan1, subscan2, non_bw_detected);
non_bw = true;
}
else if(out_img.format() == QImage::Format_RGB32
|| out_img.format() == QImage::Format_ARGB32)
{
mixed_detected = GenerateSubscans<uint32_t>(out_img, subscan1, subscan2, non_bw_detected);
non_bw = true;
}
else if(out_img.format() == QImage::Format_Mono)
{
TiffWriter::writeImage(out_file_path1, out_img);
non_bw = false;
}
if (non_bw)
{
if (mixed_detected)
{
TiffWriter::writeImage(out_file_path1, subscan1);
if (non_bw_detected) // preventing the generation of blank backgrounds
// in case of pure black-and-white set as "mixed"
TiffWriter::writeImage(out_file_path2, subscan2);
}
else
{
if(out_img.format() == QImage::Format_Mono)
TiffWriter::writeImage(out_file_path1, out_img);
else
TiffWriter::writeImage(out_file_path2, out_img);
}
}
}
else
{
TiffWriter::writeImage(out_file_path_no_split, out_img);
}
m_p_export_dialog->StepProgress();
m_pos_export++;
return 0;
}
void
MainWindow::ExportOutput(QString export_dir_path, bool default_out_dir, bool split_subscans)
{
if (isBatchProcessingInProgress())
{
QMessageBox::critical(0, "Error", tr("Batch processing is in the progress."));
return;
}
if (!isProjectLoaded())
{
QMessageBox::critical(0, "Error", tr("No project is loaded."));
return;
}
m_ptrInteractiveQueue->cancelAndClear();
if (m_ptrBatchQueue.get()) // Should not happen, but just in case.
m_ptrBatchQueue->cancelAndClear();
// Checking whether all the output thumbnails don't have a question mark on them
std::auto_ptr<ThumbnailSequence> ptrThumbSequence;
ptrThumbSequence.reset(new ThumbnailSequence(m_maxLogicalThumbSize));
if (m_ptrThumbnailCache.get()) {
IntrusivePtr<CompositeCacheDrivenTask> const task(
createCompositeCacheDrivenTask(5)
);
ptrThumbSequence->setThumbnailFactory(
IntrusivePtr<ThumbnailFactory>(
new ThumbnailFactory(
m_ptrThumbnailCache,
m_maxLogicalThumbSize, task
)
)
);
}
ptrThumbSequence->reset(
m_ptrPages->toPageSequence(m_ptrStages->filterAt(5)->getView()),
ThumbnailSequence::RESET_SELECTION, currentPageOrderProvider()
);
if (!ptrThumbSequence->AllThumbnailsComplete()) return;
// Getting the output filenames
if (default_out_dir)
{
m_export_dir = m_outFileNameGen.outDir() + "\\export";
}
else
{
m_export_dir = export_dir_path + "\\export";
}
QDir().mkdir(m_export_dir);
QString text_dir = m_export_dir + "\\1"; //folder for foreground subscans
QString pic_dir = m_export_dir + "\\2"; //folder for background subscans
m_split_subscans = split_subscans;
if (split_subscans)
{
QDir().mkdir(text_dir);
QDir().mkdir(pic_dir);
}
std::vector<PageId::SubPage> erase_variations;
erase_variations.reserve(3);
PageSequence const& pages = allPages(); // get all the pages (input pages)
unsigned const count = pages.numPages(); // input pages number
PageId page_id;
m_outpaths_vector.clear();
for (unsigned i = 0; i < count; ++i)
{
page_id = pages.pageAt(i).id();
erase_variations.clear();
switch (page_id.subPage())
{
case PageId::SINGLE_PAGE:
erase_variations.push_back(PageId::LEFT_PAGE);
erase_variations.push_back(PageId::RIGHT_PAGE);
break;
case PageId::LEFT_PAGE:
erase_variations.push_back(PageId::LEFT_PAGE);
break;
case PageId::RIGHT_PAGE:
erase_variations.push_back(PageId::RIGHT_PAGE);
break;
}
BOOST_FOREACH(PageId::SubPage subpage, erase_variations)
{
QString out_file_path = m_outFileNameGen.filePathFor(PageId(page_id.imageId(), subpage));
m_outpaths_vector.append(out_file_path);
}
}
// exporting pages
m_pos_export = 0;
m_p_export_dialog->setCount(m_outpaths_vector.size());
m_exportTimerId = startTimer(0);
}
void
MainWindow::ExportStop()
{
killTimer(m_exportTimerId);
m_exportTimerId = 0;
m_p_export_dialog->reset();
QMessageBox::information(0, "Information", tr("The files export is stopped by the user."));
}
void
MainWindow::SetStartExport()
{
if (isBatchProcessingInProgress())
{
QMessageBox::critical(0, "Error", tr("Batch processing is in the progress."));
return;
}
if (!isProjectLoaded())
{
QMessageBox::critical(0, "Error", tr("No project is loaded."));
return;
}
m_p_export_dialog->setStartExport();
}
// end of added by monday2000
........
void
MainWindow::updateWindowTitle()
{
QString project_name;
if (m_projectFile.isEmpty()) {
project_name = tr("Unnamed");
} else {
project_name = QFileInfo(m_projectFile).baseName();
}
QString const version(QString::fromUtf8(VERSION));
//begin of added by monday2000
setWindowTitle(tr("%2 - Scan Tailor (clone by monday2000) %3 [%1bit]").arg(sizeof(void*)*8).arg(project_name, version));
//end of added by monday2000
}
..........
Сборка:
http://rghost.ru/43427492 (4,5 МБ)